Bill sign-ups through the API and follow up when an invoice is paid - a lead that pays an invoice in full becomes a client however it is paid (online, autopay, a recorded payment or a batch), with the new A lead that pays an invoice becomes a client switch in Admin > Settings > Invoices and billing, on by default; a non-billable open ticket tied to an invoice (one raised through the API with ticket_invoice_id, or a quote's tickets once it becomes the invoice) gets an internal note when the invoice is paid in full, goes from On Hold back to Open, and its assigned agent is notified in the app and by email, while billable tickets whose time is on an invoice are left alone; new API endpoints recurring_invoices/create (lines from products or spelled out, the client's currency, frequency, start date, optional first invoice made at once and emailed to the primary and billing contacts, or marked Sent so its link can be paid, the answer including the invoice reference, amount, due date and pay link), recurring_invoices/read and portal_invites/create (sent from the company rather than the key's agent); tickets/create takes ticket_status (an active status other than Resolved and Closed), ticket_invoice_id (one of the client's own invoices) and ticket_notify agents to tell only the team; database 3.5.9 adds settings.config_invoice_paid_lead_to_client; invoicePaidFollowUp() runs from triggerCustomAction() on invoice_pay, with recurringInvoiceCreate(), billingInvoiceIssue() and the paid kind of ticketAgentEmailContent() in support; the Clients list reads each contract's term, so Renews soon shows again and its PHP warning is gone; changelog updated

This commit is contained in:
johnnyq
2026-11-05 19:00:16 -05:00
parent 294352fd8b
commit b5ceeb4eaf
15 changed files with 465 additions and 10 deletions

View File

@@ -0,0 +1,22 @@
<?php
/*
* ITFlow - Database update to version 3.5.9 (from 3.5.8)
* Included by admin/database_updates.php - do not access directly
*
* settings.config_invoice_paid_lead_to_client - a lead that pays an invoice in full
* becomes a client (invoicePaidFollowUp(), functions/billing.php). On by default:
* a lead that pays is a client.
*
* Re-runnable: the column is made only where it isn't there yet.
*/
defined('FROM_DB_UPDATER') || die("Direct file access is not allowed");
$itflow_column_exists = mysqli_query($mysqli, "SELECT 1 FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'settings' AND COLUMN_NAME = 'config_invoice_paid_lead_to_client' LIMIT 1");
if (!$itflow_column_exists || mysqli_num_rows($itflow_column_exists) === 0) {
mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_invoice_paid_lead_to_client` tinyint(1) NOT NULL DEFAULT 1 AFTER `config_invoice_paid_notification_email`");
}
unset($itflow_column_exists);

View File

@@ -3,7 +3,8 @@
/*
* Admin > Settings > Invoices and billing (admin/settings_invoice.php):
* edit_invoice_settings saves the numbering, the footer and tax ID, late
* fees, who is emailed an invoice paid online, payment reminders and
* fees, who is emailed an invoice paid online and whether a lead that pays
* becomes a client, payment reminders and
* statements, how time is billed, the markup billable expenses start with,
* and trips and mileage.
*
@@ -45,6 +46,11 @@ if (isset($_POST['edit_invoice_settings'])) {
foreach (['config_invoice_show_tax_id', 'config_invoice_late_fee_enable', 'config_send_invoice_reminders', 'config_statement_monthly', 'config_billing_email_attach_pdf'] as $switch) {
$after[$switch] = empty($_POST[$switch]) ? 0 : 1;
}
// From 3.5.9 - a lead that pays becomes a client (invoicePaidFollowUp())
if (settingsColumnExists($mysqli, 'config_invoice_paid_lead_to_client')) {
$before += settingsCurrent(['config_invoice_paid_lead_to_client']);
$after['config_invoice_paid_lead_to_client'] = empty($_POST['config_invoice_paid_lead_to_client']) ? 0 : 1;
}
// Late fees - a percent of what is owed
$late_fee = billingNumberInput(str_replace('%', '', (string) $in['config_invoice_late_fee_percent']));

View File

@@ -73,6 +73,10 @@ echo settingsPageStart('invoice');
</div>
<?= settingsInput('config_invoice_paid_notification_email', $config_invoice_paid_notification_email, ['icon' => 'fa-bell', 'type' => 'email', 'maxlength' => 200,
'placeholder' => 'billing@company.com', 'help' => 'A shared mailbox or list works best. Empty, nobody is emailed - the payment is still recorded.']) ?>
<?php if (settingsColumnExists($mysqli, 'config_invoice_paid_lead_to_client')) { ?>
<?= settingsSwitch('config_invoice_paid_lead_to_client', settingsCurrent(['config_invoice_paid_lead_to_client'])['config_invoice_paid_lead_to_client'],
['help' => 'However it is paid in full - online, by autopay or a payment you record. A ticket linked to the invoice that is On Hold opens again at the same time, and its agent is told.']) ?>
<?php } ?>
<?= settingsSectionEnd() ?>
<?= settingsSectionStart('reminders', 'Reminders and statements', 'Reminders go to each client\'s primary and billing contacts while an invoice is still owed, with a button to view and pay it. A client can be left out of reminders and statements on the Billing tab of its Edit window.', 'reminders') ?>