Files
itflow/agent/modals/payment/payment.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

191 lines
11 KiB
PHP

<?php
/*
* A payment's quick look - the invoice it paid (status, what it was for, what
* it came to, what has been paid on it in all and what is left), the other
* payments on it, where the money went and how it was paid. One paid online
* shows the payment provider's fee and what came in after it, when the fee
* is in the expenses with the payment's reference. Open the
* invoice, email the receipt again, Edit and Delete. Opened from the Income
* list, Transactions and the reports. ?id=
*/
require_once '../../../includes/modal_header.php';
enforceUserPermission('module_financial');
$row = incomePaymentRow(intval($_GET['id'] ?? 0));
if (!$row) {
echo json_encode(['error' => 'That payment no longer exists, or is not one you can see.']);
exit;
}
$payment_id = intval($row['payment_id']);
$cleared = reconcileLock('payment', $payment_id);
$deposited = transferDepositedIn('payment', $payment_id);
$client_id = intval($row['invoice_client_id']);
$amount = floatval($row['payment_amount']);
$currency = (string) ($row['payment_currency_code'] ?: $row['invoice_currency_code']);
$has_invoice = $row['invoice_number'] !== null;
$can_sales = lookupUserPermission('module_sales') >= 1;
$can_edit = incomeCanEditPayments() && $has_invoice;
$can_delete = incomeCanDeletePayments() && $has_invoice;
$online = preg_match('/^Stripe - pi_/', (string) $row['payment_reference']) === 1;
$state = $has_invoice ? invoiceState($row, $row['invoice_paid']) : null;
$can_receipt = $has_invoice && billingMoneyCan('receipt') && $client_id;
$receipt_to = $can_receipt ? array_column(billingEmailRecipients($client_id), 'email') : [];
$money = function ($value, $code = null) use ($currency) {
return escapeHtml(billingMoney($value, $code ?? $currency));
};
ob_start();
?>
<div class="modal-header bg-dark" data-bs-theme="dark">
<div class="d-flex align-items-center gap-3 min-w-0 flex-grow-1">
<span class="itflow-asset-icon itflow-asset-icon-lg bg-success-subtle text-success-emphasis" aria-hidden="true"><i class="fas fa-file-invoice-dollar"></i></span>
<div class="min-w-0 flex-grow-1">
<h5 class="modal-title fw-semibold text-break"><?= $has_invoice ? 'Payment on ' . escapeHtml($row['invoice_prefix'] . $row['invoice_number']) : 'Payment' ?></h5>
<div class="small text-body-secondary mt-1 text-truncate"><?= escapeHtml(date('D, M j, Y', strtotime($row['payment_date']))) ?><?= $row['client_name'] !== null ? ' &middot; ' . escapeHtml($row['client_name']) : '' ?></div>
</div>
<div class="text-end">
<div class="fs-4 fw-semibold text-white text-nowrap"><?= $money($amount) ?></div>
<?php if ($online) { ?>
<div class="small text-body-secondary"><i class="fas fa-globe me-1"></i>Paid online</div>
<?php } ?>
</div>
</div>
<button type="button" class="btn-close btn-close-white ms-3" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- The invoice it paid -->
<?php if ($has_invoice) { ?>
<div class="p-3 rounded border bg-body-tertiary mb-3">
<div class="d-flex flex-wrap align-items-start gap-3">
<div class="flex-grow-1 min-w-0">
<div class="fw-semibold">
<?php if ($can_sales) { ?>
<a href="invoice.php?invoice_id=<?= intval($row['invoice_id']) ?>"><?= escapeHtml($row['invoice_prefix'] . $row['invoice_number']) ?></a>
<?php } else { ?>
<?= escapeHtml($row['invoice_prefix'] . $row['invoice_number']) ?>
<?php } ?>
<span class="ms-1"><?= invoiceStatusBadge($row['invoice_status']) ?></span>
<?= invoiceDuePill($state) ?>
</div>
<?php if (trim((string) $row['invoice_scope']) !== '') { ?>
<div class="small text-body-secondary text-truncate mt-1"><?= escapeHtml($row['invoice_scope']) ?></div>
<?php } ?>
<div class="small text-body-secondary mt-1">Dated <?= escapeHtml($row['invoice_date']) ?>, due <?= escapeHtml($row['invoice_due']) ?></div>
</div>
<div class="text-end small">
<div><span class="text-body-secondary">Invoice</span> <span class="itflow-money fw-semibold ms-2"><?= $money($row['invoice_amount'], $row['invoice_currency_code']) ?></span></div>
<div><span class="text-body-secondary">Paid in all</span> <span class="itflow-money ms-2"><?= $money($row['invoice_paid'], $row['invoice_currency_code']) ?></span></div>
<div class="<?= $state['balance'] > 0 ? 'text-danger-emphasis' : 'text-success-emphasis' ?>">
<span><?= $state['balance'] > 0 ? 'Still owed' : 'Nothing owed' ?></span>
<?php if ($state['balance'] > 0) { ?><span class="itflow-money fw-semibold ms-2"><?= $money($state['balance'], $row['invoice_currency_code']) ?></span><?php } ?>
</div>
</div>
</div>
<?php if ($row['others']) { ?>
<div class="border-top mt-3 pt-2">
<div class="small fw-semibold text-uppercase text-body-secondary mb-1">The other payment<?= count($row['others']) === 1 ? '' : 's' ?> on it</div>
<?php foreach ($row['others'] as $other) { ?>
<div class="d-flex justify-content-between small">
<a href="#" class="ajax-modal" data-modal-size="lg" data-modal-url="modals/payment/payment.php?id=<?= intval($other['payment_id']) ?>"><?= escapeHtml($other['payment_date']) ?><?= $other['payment_method'] ? ' - ' . escapeHtml($other['payment_method']) : '' ?></a>
<span class="itflow-money"><?= $money($other['payment_amount'], $other['payment_currency_code'] ?: $row['invoice_currency_code']) ?></span>
</div>
<?php } ?>
</div>
<?php } ?>
</div>
<?php } else { ?>
<div class="alert alert-warning small">The invoice this was paid on has since been deleted.</div>
<?php } ?>
<!-- At a glance -->
<div class="row g-2 itflow-asset-glance">
<div class="col-sm-6">
<div class="itflow-glance-item" title="Client">
<i class="fas fa-fw fa-building"></i>
<?php if ($client_id && $row['client_name'] !== null) { ?>
<a class="text-truncate" href="income.php?client_id=<?= $client_id ?>" title="What came in from them"><?= escapeHtml($row['client_name']) ?></a>
<?php } else { ?>
<span class="text-body-secondary">No client</span>
<?php } ?>
</div>
<div class="itflow-glance-item" title="Category - the invoice's">
<i class="fas fa-fw fa-tag"></i>
<?php if (intval($row['invoice_category_id']) && $row['category_name'] !== null) { ?>
<span class="text-truncate"><?= expenseCategoryDot($row['category_color'], $row['category_icon'] ?? '') ?><?= escapeHtml($row['category_name']) ?></span>
<?php } else { ?>
<span class="text-warning-emphasis">No category<?= $has_invoice ? ' - set one on the invoice' : '' ?></span>
<?php } ?>
</div>
<div class="itflow-glance-item" title="Entered">
<i class="fas fa-fw fa-clock"></i>
<span><span title="<?= escapeHtml($row['payment_created_at']) ?>"><?= timeAgo($row['payment_created_at']) ?></span><?php if (!empty($row['payment_import_key'])) { ?> <span class="text-body-secondary">from a bank statement</span><?php } ?></span>
</div>
<?= transferDepositedGlanceHtml($deposited) ?>
<?= reconcileGlanceHtml($cleared) ?>
</div>
<div class="col-sm-6">
<div class="itflow-glance-item" title="Paid into">
<i class="fas fa-fw fa-piggy-bank"></i>
<span class="text-truncate"><?= escapeHtml($row['account_name'] ?: 'No account') ?><?= $row['account_archived_at'] ? ' <span class="text-body-secondary">(archived)</span>' : '' ?></span>
</div>
<div class="itflow-glance-item" title="Paid by">
<i class="fas fa-fw fa-credit-card"></i>
<span><?= escapeHtml($row['payment_method'] ?: '-') ?></span>
</div>
<?php if (trim((string) $row['payment_reference']) !== '') { ?>
<div class="itflow-glance-item" title="Reference">
<i class="fas fa-fw fa-hashtag"></i>
<span class="font-monospace text-truncate"><?= escapeHtml($row['payment_reference']) ?></span>
</div>
<?php } ?>
</div>
</div>
<!-- Paid online: what the payment provider kept, when its fee is in the expenses -->
<?php if ($online && $row['fee']) {
$fee = floatval($row['fee']['expense_amount']); ?>
<div class="d-flex flex-wrap align-items-center gap-3 mt-3 p-2 px-3 rounded border">
<i class="fas fa-globe text-info"></i>
<div class="flex-grow-1 small">
The payment provider's fee was <a href="#" class="ajax-modal" data-modal-size="lg" data-modal-url="modals/expense/expense.php?id=<?= intval($row['fee']['expense_id']) ?>"><?= $money($fee, $row['fee']['expense_currency_code'] ?: $currency) ?></a>
- <strong><?= $money($amount - $fee) ?></strong> came in after it
</div>
</div>
<?php } ?>
</div>
<div class="modal-footer">
<?php if ($has_invoice && $can_sales) { ?>
<a class="btn btn-primary text-bold" href="invoice.php?invoice_id=<?= intval($row['invoice_id']) ?>"><i class="fas fa-file-invoice me-2"></i>Open Invoice</a>
<?php } ?>
<?php if ($can_receipt) { ?>
<a class="btn btn-outline-secondary confirm-link <?= $receipt_to ? '' : 'disabled' ?>" href="post.php?payment_send_receipt=<?= $payment_id ?>&amp;csrf_token=<?= $_SESSION['csrf_token'] ?>"
title="<?= $receipt_to ? 'To ' . escapeHtml(implode(', ', $receipt_to)) : 'No billing contact with an email' ?>"
data-confirm-text="The receipt for <?= $money($amount) ?> goes to <?= escapeHtml(implode(', ', $receipt_to)) ?>."><i class="fas fa-paper-plane"></i><span class="d-none d-sm-inline ms-2">Email Receipt</span></a>
<?php } ?>
<?php if ($can_edit) { ?>
<button type="button" class="btn btn-outline-secondary ajax-modal" data-modal-url="modals/payment/payment_edit.php?id=<?= $payment_id ?>"><i class="fas fa-edit"></i><span class="d-none d-sm-inline ms-2">Edit</span></button>
<?php } ?>
<?php if ($can_delete) { ?>
<?php if ($cleared) { ?>
<?= reconcileLockedButtonHtml($cleared) ?>
<?php } else { ?>
<a class="btn btn-outline-danger confirm-link" href="post.php?delete_payment=<?= $payment_id ?>&amp;csrf_token=<?= $_SESSION['csrf_token'] ?>"
data-confirm-text="The payment of <?= $money($amount) ?> comes off <?= escapeHtml($row['invoice_prefix'] . $row['invoice_number']) ?>, which is owed again."><i class="fas fa-trash"></i><span class="d-none d-sm-inline ms-2">Delete</span></a>
<?php } ?>
<?php } ?>
<button type="button" class="btn btn-light" data-bs-dismiss="modal"><i class="fa fa-times me-2"></i>Close</button>
</div>
<?php
require_once '../../../includes/modal_footer.php';