Files
itflow/agent/modals/payment/payment_bulk_add.php
johnnyq be002b6f29 Roles stage 4: the money on invoices goes by Financial with Sales at Read through billingMoneyCan and enforceBillingMoney, so taking payments one at a time, by saved card or as a batch, changing them and emailing receipts, reminders and statements need write access to Financial, and deleting or refunding a payment needs full access, with the old Sales rules for reminders, statements and receipts still accepted; invoice, invoice list, Income and payment quick looks offer only what the role can do, the reminder window offers only the reminder to a role that cannot send the invoice, a reminder for a draft or with nothing owed still needs Sales, and Income bulk Set Category on payments needs Sales as it changes the invoice; database 3.1.8 gives the built-in Accountant full access to Financial when it still has its shipped levels, and new installs seed it that way; calendar events for a client follow the role's Clients level through calendarEventCan, Read to see, Modify to add, change, move, skip or send, Full to delete or Modify for the agent who added it, events an agent is on always show, events with no client stay open to everyone, and the calendar, dashboard and calendar links leave out other agents' client events for roles without Clients, with New event, the client picker and dragging following the same rule; the API now sets PHP and MySQL to ITFlow's timezone so what it creates, API ticket SLA times and key expiry go by local time, and inc_set_timezone.php only writes the session when one is open and no longer leaves its result and row variables behind; changelog updated
2026-10-17 21:52:10 -04:00

86 lines
4.2 KiB
PHP

<?php
/*
* Batch payment - one payment from a client spread over their unpaid
* invoices, oldest first, each paid in full until it runs out. Shows the
* invoices it will go to. ?client_id=
*/
require_once '../../../includes/modal_header.php';
enforceBillingMoney('take');
$client_id = intval($_GET['client_id'] ?? 0);
enforceClientAccess();
$client = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT client_name, client_currency_code FROM clients WHERE client_id = $client_id"));
if (!$client) {
exit(json_encode(['error' => 'That client no longer exists.']));
}
// What the batch pays, in the order the batch pays it
$open = [];
$owed = 0;
$sql = mysqli_query($mysqli, "SELECT invoice_id, invoice_prefix, invoice_number, invoice_scope, invoice_due, invoice_amount, invoice_currency_code,
invoice_amount - COALESCE((SELECT SUM(payment_amount) FROM payments WHERE payment_invoice_id = invoice_id), 0) AS balance
FROM invoices WHERE invoice_client_id = $client_id AND invoice_status IN ('Sent', 'Viewed', 'Partial')
ORDER BY invoice_number ASC");
while ($row = mysqli_fetch_assoc($sql)) {
if (floatval($row['balance']) > 0) {
$open[] = $row;
$owed += floatval($row['balance']);
}
}
$owed = round($owed, 2);
if (!$open) {
exit(json_encode(['error' => $client['client_name'] . ' has no unpaid invoices.']));
}
$currency = (string) ($open[0]['invoice_currency_code'] ?: $client['client_currency_code']);
$contact_email = (string) mysqli_fetch_row(mysqli_query($mysqli, "SELECT contact_email FROM contacts WHERE contact_client_id = $client_id AND contact_primary = 1 AND contact_archived_at IS NULL LIMIT 1"))[0];
$pay = [
'id' => 'payBatch' . $client_id,
'amount' => $owed,
'owed' => $owed,
'currency' => $currency,
'email' => $contact_email,
'full_text' => count($open) === 1 ? 'Pays it in full.' : 'Pays all ' . count($open) . ' in full.',
];
ob_start();
?>
<div class="modal-header bg-dark">
<h5 class="modal-title"><i class="fas fa-fw fa-layer-group me-2"></i>Batch Payment <span class="fw-normal text-white-50 ms-1"><?= escapeHtml($client['client_name']) ?></span></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="client_id" value="<?= $client_id ?>">
<input type="hidden" name="balance" value="<?= number_format($owed, 2, '.', '') ?>">
<input type="hidden" name="currency_code" value="<?= escapeHtml($currency) ?>">
<div class="modal-body">
<p class="small text-body-secondary">One payment spread over <?= escapeHtml($client['client_name']) ?>'s unpaid invoices, oldest first - each is paid in full until the payment runs out, and the last one it reaches may be part paid.</p>
<div class="list-group list-group-flush border rounded mb-3" style="max-height: 12rem; overflow-y: auto;">
<?php foreach ($open as $row) {
$row_state = invoiceState(['invoice_status' => 'Sent', 'invoice_amount' => $row['invoice_amount'], 'invoice_due' => $row['invoice_due']], floatval($row['invoice_amount']) - floatval($row['balance'])); ?>
<div class="list-group-item d-flex align-items-center gap-2 py-2">
<span class="fw-semibold"><?= escapeHtml($row['invoice_prefix'] . $row['invoice_number']) ?></span>
<span class="small text-body-secondary text-truncate flex-grow-1"><?= escapeHtml($row['invoice_scope']) ?></span>
<?= invoiceDuePill($row_state) ?>
<span class="itflow-money small fw-semibold"><?= escapeHtml(billingMoney($row['balance'], $row['invoice_currency_code'])) ?></span>
</div>
<?php } ?>
</div>
<?php require __DIR__ . '/payment_form.php'; ?>
</div>
<div class="modal-footer">
<button type="submit" name="add_bulk_payment" class="btn btn-success text-bold"><i class="fas fa-check me-2"></i>Add Payment</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>
<?php
require_once '../../../includes/modal_footer.php';