mirror of
https://github.com/itflow-org/itflow
synced 2026-11-11 07:01:16 +00:00
128 lines
7.2 KiB
PHP
128 lines
7.2 KiB
PHP
<?php
|
|
|
|
/*
|
|
* 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 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.
|
|
*
|
|
* A next number already in use, an email address that isn't one, or an
|
|
* amount, percent or distance that isn't one comes back to the page saying
|
|
* so beside it - nothing is saved. Choices from a list settle to one of
|
|
* them, and reminder days to the ones that can be (billingReminderDaysParse()),
|
|
* as they always have. A setting not sent at all keeps what it is; a switch
|
|
* not sent is off.
|
|
*/
|
|
|
|
defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
|
|
|
|
if (isset($_POST['edit_invoice_settings'])) {
|
|
|
|
validateCSRFToken();
|
|
|
|
$before = settingsCurrent(['config_invoice_prefix', 'config_invoice_next_number', 'config_recurring_invoice_prefix', 'config_recurring_invoice_next_number',
|
|
'config_invoice_footer', 'config_invoice_show_tax_id', 'config_invoice_late_fee_enable', 'config_invoice_late_fee_percent', 'config_invoice_paid_notification_email',
|
|
'config_send_invoice_reminders', 'config_invoice_reminder_days', 'config_invoice_reminder_repeat', 'config_statement_monthly', 'config_statement_day', 'config_billing_email_attach_pdf',
|
|
'config_time_increment', 'config_time_rounding', 'config_time_minimum', 'config_time_item_name', 'config_expense_markup',
|
|
'config_mileage_unit', 'config_mileage_rate', 'config_mileage_category_id', 'config_trip_charge', 'config_trip_bill_rate', 'config_trip_included', 'config_trip_item_name']);
|
|
|
|
// What was sent, and what is saved for anything that wasn't
|
|
$in = $_POST + array_map(function ($value) { return (string) $value; }, $before);
|
|
|
|
[$invoice_prefix, $invoice_number, $errors] = settingsNumberingCheck('invoice', $in, $before);
|
|
[$recurring_prefix, $recurring_number, $recurring_errors] = settingsNumberingCheck('recurring_invoice', $in, $before);
|
|
$errors += $recurring_errors;
|
|
|
|
$after = [
|
|
'config_invoice_prefix' => $invoice_prefix,
|
|
'config_invoice_next_number' => $invoice_number,
|
|
'config_recurring_invoice_prefix' => $recurring_prefix,
|
|
'config_recurring_invoice_next_number' => $recurring_number,
|
|
'config_invoice_footer' => rtrim(str_replace("\r\n", "\n", (string) $in['config_invoice_footer'])),
|
|
'config_invoice_paid_notification_email' => trim((string) $in['config_invoice_paid_notification_email']),
|
|
];
|
|
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']));
|
|
if ($late_fee === null || $late_fee < 0 || $late_fee > 100) {
|
|
$errors['config_invoice_late_fee_percent'] = 'A percent, from 0 to 100.';
|
|
}
|
|
$after['config_invoice_late_fee_percent'] = $late_fee;
|
|
|
|
if ($problem = settingsEmailProblem($after['config_invoice_paid_notification_email'])) {
|
|
$errors['config_invoice_paid_notification_email'] = $problem;
|
|
}
|
|
|
|
// Reminders and statements - see billingReminderRules(). Days before the due date
|
|
// are stored negative, the due date itself as 0, days late positive.
|
|
if (isset($_POST['reminder_before']) || isset($_POST['reminder_after'])) {
|
|
$reminder_days = array_merge(
|
|
array_map(function ($day) { return -$day; }, array_filter(billingReminderDaysParse($_POST['reminder_before'] ?? ''), function ($day) { return $day > 0 && $day <= 60; })),
|
|
!empty($_POST['reminder_on_due']) ? [0] : [],
|
|
array_filter(billingReminderDaysParse($_POST['reminder_after'] ?? ''), function ($day) { return $day > 0; })
|
|
);
|
|
$after['config_invoice_reminder_days'] = implode(',', billingReminderDaysParse(implode(',', $reminder_days)));
|
|
}
|
|
$after['config_invoice_reminder_repeat'] = max(0, min(365, intval($_POST['reminder_repeat'] ?? $before['config_invoice_reminder_repeat'])));
|
|
$after['config_statement_day'] = max(1, min(28, intval($in['config_statement_day'])));
|
|
|
|
// Billing time - see billingTimeRules()
|
|
$increment = intval($in['config_time_increment']);
|
|
$after['config_time_increment'] = isset(billingTimeIncrements()[$increment]) ? $increment : 15;
|
|
$after['config_time_rounding'] = $in['config_time_rounding'] === 'up' ? 'up' : 'nearest';
|
|
$after['config_time_minimum'] = max(0, min(1440, intval($in['config_time_minimum'])));
|
|
$time_item = trim((string) $in['config_time_item_name']);
|
|
$after['config_time_item_name'] = mb_substr($time_item !== '' ? $time_item : 'Support [Hourly]', 0, 200);
|
|
|
|
// Billable expenses - the markup they start with, a percent
|
|
$markup = billingNumberInput(str_replace('%', '', (string) $in['config_expense_markup']));
|
|
if ($markup === null || $markup < 0 || $markup > 1000) {
|
|
$errors['config_expense_markup'] = 'The markup is a percent, from 0 to 1000.';
|
|
}
|
|
$after['config_expense_markup'] = $markup;
|
|
|
|
// Trips and mileage - see tripRules()
|
|
$after['config_mileage_unit'] = $in['config_mileage_unit'] === 'km' ? 'km' : 'mi';
|
|
foreach (['config_mileage_rate', 'config_trip_bill_rate'] as $rate) {
|
|
$after[$rate] = tripRateInput($in[$rate]);
|
|
if ($after[$rate] === null) {
|
|
$errors[$rate] = 'The rates are amounts, such as 0.70.';
|
|
}
|
|
}
|
|
$after['config_trip_charge'] = billingNumberInput($in['config_trip_charge']);
|
|
if ($after['config_trip_charge'] === null || $after['config_trip_charge'] < 0 || $after['config_trip_charge'] >= 1000000) {
|
|
$errors['config_trip_charge'] = 'An amount, such as 45.00.';
|
|
}
|
|
$included = billingNumberInput($in['config_trip_included']);
|
|
if ($included === null || $included < 0 || $included >= 100000) {
|
|
$errors['config_trip_included'] = 'A distance, such as 20.';
|
|
}
|
|
$after['config_trip_included'] = $included === null ? null : round($included, 1);
|
|
$category_id = intval($in['config_mileage_category_id']);
|
|
if ($category_id && !mysqli_num_rows(mysqli_query($mysqli, "SELECT category_id FROM categories WHERE category_id = $category_id AND category_type = 'Expense'"))) {
|
|
$category_id = 0;
|
|
}
|
|
$after['config_mileage_category_id'] = $category_id;
|
|
$trip_item = trim((string) $in['config_trip_item_name']);
|
|
$after['config_trip_item_name'] = mb_substr($trip_item !== '' ? $trip_item : 'Travel', 0, 200);
|
|
|
|
if ($errors) {
|
|
settingsRefuse('invoice', $errors);
|
|
}
|
|
|
|
settingsSave('invoice', $before, $after);
|
|
|
|
redirect();
|
|
|
|
}
|