mirror of
https://github.com/itflow-org/itflow
synced 2026-11-13 07:57:25 +00:00
359 lines
24 KiB
PHP
359 lines
24 KiB
PHP
<?php
|
|
|
|
/*
|
|
* The document itself - an invoice, quote or recurring invoice as the
|
|
* client sees it, on the agent pages and the guest pages alike, and as it
|
|
* prints. Set $paper before including:
|
|
*
|
|
* type invoice | quote | recurring
|
|
* row billingRow()
|
|
* items billingItems()
|
|
* totals billingTotals()
|
|
* paid what has been paid (invoices)
|
|
* balance what is still owed (invoices)
|
|
* edit the lines can be changed here (agent pages): drag handles,
|
|
* a menu on each line, the add field, and a line's name,
|
|
* description, quantity, price and tax changed where they sit
|
|
* (js/billing_items.js)
|
|
* notes_edit the notes save in place (agent pages)
|
|
* taxes billingTaxes(), the rates a line's tax is picked from
|
|
* logo_url the company logo, relative to the page
|
|
* client shown to the client (the client portal) - no agent-only notes
|
|
* csrf the agent's token, for the edit controls
|
|
* ai Write it with AI under the add field (quotes and
|
|
* invoices, when the agent may use AI for Billing)
|
|
*
|
|
* Everything in it is escaped here.
|
|
*/
|
|
|
|
isset($paper) || die("Direct file access is not allowed");
|
|
|
|
$paper_type = $paper['type'];
|
|
$paper_t = billingType($paper_type);
|
|
$paper_c = $paper_t['col'];
|
|
$paper_row = $paper['row'];
|
|
$paper_items = $paper['items'];
|
|
$paper_totals = $paper['totals'];
|
|
$paper_edit = !empty($paper['edit']);
|
|
$paper_currency = (string) ($paper_row["{$paper_c}_currency_code"] ?? '');
|
|
$paper_id = intval($paper_row["{$paper_c}_id"]);
|
|
$paper_company = billingCompany();
|
|
$paper_bill_to = billingBillTo($paper_row['client_id']);
|
|
$paper_status = $paper_type === 'recurring' ? (intval($paper_row['recurring_invoice_status']) ? 'Active' : 'Paused') : (string) $paper_row["{$paper_c}_status"];
|
|
$paper_money = function ($amount) use ($paper_currency) {
|
|
return escapeHtml(billingMoney($amount, $paper_currency));
|
|
};
|
|
$paper_title = ['invoice' => 'Invoice', 'quote' => 'Quote', 'recurring' => 'Recurring Invoice'][$paper_type];
|
|
$paper_footer = $paper_type === 'quote' ? ($GLOBALS['config_quote_footer'] ?? '') : ($GLOBALS['config_invoice_footer'] ?? '');
|
|
|
|
// A stamp across a document that is done with
|
|
$paper_stamp = '';
|
|
if ($paper_type === 'invoice' && $paper_status === 'Paid') {
|
|
$paper_stamp = ['Paid', 'success'];
|
|
} elseif ($paper_type === 'invoice' && $paper_status === 'Cancelled') {
|
|
$paper_stamp = ['Cancelled', 'danger'];
|
|
} elseif ($paper_type === 'quote' && in_array($paper_status, ['Accepted', 'Invoiced'], true)) {
|
|
$paper_stamp = ['Accepted', 'success'];
|
|
} elseif ($paper_type === 'quote' && $paper_status === 'Declined') {
|
|
$paper_stamp = ['Declined', 'danger'];
|
|
}
|
|
|
|
$paper_dates = [];
|
|
if ($paper_type === 'invoice') {
|
|
$paper_dates = ['Invoice date' => $paper_row['invoice_date'], 'Due' => $paper_row['invoice_due']];
|
|
} elseif ($paper_type === 'quote') {
|
|
$paper_dates = ['Quote date' => $paper_row['quote_date'], 'Valid until' => $paper_row['quote_expire']];
|
|
} else {
|
|
$paper_dates = ['Billed' => recurringInvoiceFrequencies()[$paper_row['recurring_invoice_frequency']] ?? ucfirst((string) $paper_row['recurring_invoice_frequency']),
|
|
'Next invoice' => $paper_row['recurring_invoice_next_date']];
|
|
}
|
|
|
|
// A recurring invoice's seat lines - what each counts now, and whether the line is off
|
|
$paper_seats = $paper_type === 'recurring' ? recurringSeatLines($paper_id, intval($paper_row['client_id'])) : [];
|
|
$paper_seat_sources = recurringSeatSources();
|
|
|
|
// A quote with lines billed each month, quarter or year shows them apart from
|
|
// what is billed once - the lines under a heading each, and a total each
|
|
$paper_groups = $paper_type === 'quote' && !empty($paper_totals['recurring']) ? $paper_totals['groups'] : null;
|
|
$paper_line_groups = $paper_groups ?: ['' => ['frequency' => '', 'label' => '', 'items' => $paper_items]];
|
|
|
|
// Where the lines can be changed, what can be changed where it sits is wrapped
|
|
// as a field: which one, its value as it is typed, and what a click on it
|
|
// does. And the parts of the paper that follow the lines are marked, to be
|
|
// drawn again after a change.
|
|
$paper_field = function ($field, $value, $what, $html, $class = '') use ($paper_edit) {
|
|
return $paper_edit
|
|
? '<span class="itflow-line-field' . ($class !== '' ? " $class" : '') . '" data-line-field="' . $field . '" data-value="' . escapeHtml($value) . '" tabindex="0" role="button" title="Change the ' . $what . '">' . $html . '</span>'
|
|
: $html;
|
|
};
|
|
$paper_live = function ($part) use ($paper_edit) {
|
|
return $paper_edit ? ' data-billing-live="' . $part . '"' : '';
|
|
};
|
|
|
|
$paper_post = function ($params) use ($paper_type, $paper_id, $paper) {
|
|
return 'post.php?' . http_build_query(array_merge($params, ['type' => $paper_type, 'doc_id' => $paper_id, 'csrf_token' => $paper['csrf'] ?? '']));
|
|
};
|
|
$paper_company_lines = array_merge(array_filter([
|
|
formatAddress($paper_company['company_address'] ?? '', $paper_company['company_city'] ?? '', $paper_company['company_state'] ?? '', $paper_company['company_zip'] ?? '', $paper_company['company_country'] ?? '', "\n"),
|
|
implode(' · ', array_filter([$paper_company['company_email'] ?? '', $paper_company['phone_display'] ?? ''])),
|
|
$paper_company['company_website'] ?? '',
|
|
!empty($GLOBALS['config_invoice_show_tax_id']) && !empty($paper_company['company_tax_id']) ? 'Tax ID: ' . $paper_company['company_tax_id'] : '',
|
|
]), billingTaxRegistrationLines($paper_totals));
|
|
$paper_bill_lines = array_filter([
|
|
formatAddress($paper_bill_to['location_address'] ?? '', $paper_bill_to['location_city'] ?? '', $paper_bill_to['location_state'] ?? '', $paper_bill_to['location_zip'] ?? '', $paper_bill_to['location_country'] ?? '', "\n"),
|
|
$paper_bill_to['contact_name'] ?? '',
|
|
implode(' · ', array_filter([$paper_bill_to['contact_email'] ?? '', $paper_bill_to['contact_phone'] ?? ''])),
|
|
!empty($paper_bill_to['client_tax_id_number']) ? 'Tax ID: ' . $paper_bill_to['client_tax_id_number'] : '',
|
|
// Exempt - said only when nothing here is taxed, so an older taxed invoice doesn't contradict itself
|
|
!empty($paper_bill_to['tax_exempt_text']) && $paper_totals['tax'] == 0 ? $paper_bill_to['tax_exempt_text'] : '',
|
|
]);
|
|
|
|
?>
|
|
<div class="itflow-paper" id="items" data-billing-type="<?= $paper_type ?>" data-billing-id="<?= $paper_id ?>"<?= $paper_edit ? ' data-billing-edit' : '' ?>>
|
|
|
|
<?php if ($paper_stamp) { ?>
|
|
<div class="itflow-paper-stamp text-<?= $paper_stamp[1] ?>" aria-hidden="true"><?= $paper_stamp[0] ?></div>
|
|
<?php } ?>
|
|
|
|
<!-- Who it is from, and what it is -->
|
|
<div class="itflow-paper-head">
|
|
<div class="itflow-paper-from">
|
|
<?php if (!empty($paper['logo_url']) && !empty($paper_company['logo_url'])) { ?>
|
|
<img class="itflow-paper-logo" src="<?= escapeHtml($paper['logo_url']) ?>" alt="<?= escapeHtml($paper_company['company_name'] ?? '') ?>">
|
|
<?php } ?>
|
|
<div class="fw-semibold fs-5"><?= escapeHtml($paper_company['company_name'] ?? '') ?></div>
|
|
<div class="small text-body-secondary"><?= nl2br(escapeHtml(implode("\n", $paper_company_lines))) ?></div>
|
|
</div>
|
|
<div class="itflow-paper-what text-md-end">
|
|
<div class="itflow-paper-title"><?= $paper_title ?></div>
|
|
<div class="fs-5 fw-semibold font-monospace"><?= escapeHtml(billingNumber($paper_type, $paper_row)) ?></div>
|
|
<dl class="itflow-paper-dates">
|
|
<?php foreach ($paper_dates as $label => $value) { ?>
|
|
<dt><?= $label ?></dt><dd><?= escapeHtml($value) ?></dd>
|
|
<?php } ?>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Who it is for, and what it comes to -->
|
|
<div class="itflow-paper-parties">
|
|
<div>
|
|
<div class="itflow-paper-label"><?= $paper_type === 'quote' ? 'Prepared for' : 'Bill to' ?></div>
|
|
<div class="fw-semibold"><?= escapeHtml($paper_bill_to['client_name'] ?? $paper_row['client_name']) ?></div>
|
|
<div class="small text-body-secondary"><?= nl2br(escapeHtml(implode("\n", $paper_bill_lines))) ?></div>
|
|
</div>
|
|
<div class="itflow-paper-due text-md-end"<?= $paper_live('due') ?>>
|
|
<?php if ($paper_type === 'invoice') { ?>
|
|
<div class="itflow-paper-label"><?= in_array($paper_status, ['Paid', 'Cancelled', 'Non-Billable'], true) ? 'Balance' : 'Amount due' ?></div>
|
|
<div class="itflow-paper-amount"><?= $paper_money($paper['balance'] ?? $paper_totals['total']) ?></div>
|
|
<?php if (!in_array($paper_status, ['Paid', 'Cancelled', 'Non-Billable', 'Draft'], true) && ($paper['balance'] ?? 0) > 0) { ?>
|
|
<div class="small text-body-secondary">by <?= escapeHtml($paper_row['invoice_due']) ?></div>
|
|
<?php } ?>
|
|
<?php } elseif ($paper_type === 'quote' && $paper_groups) {
|
|
$paper_first = true;
|
|
foreach ($paper_groups as $paper_frequency => $paper_group) {
|
|
if ($paper_frequency === '' && !$paper_group['items'] && $paper_group['discount'] == 0) {
|
|
continue;
|
|
} ?>
|
|
<div class="itflow-paper-label <?= $paper_first ? '' : 'mt-2' ?>"><?= $paper_frequency === '' ? 'Up front' : escapeHtml($paper_group['label']) ?></div>
|
|
<div class="<?= $paper_first ? 'itflow-paper-amount' : 'fs-5 fw-semibold' ?>"><?= $paper_money($paper_group['total']) ?></div>
|
|
<?php $paper_first = false;
|
|
}
|
|
} elseif ($paper_type === 'quote') { ?>
|
|
<div class="itflow-paper-label">Quote total</div>
|
|
<div class="itflow-paper-amount"><?= $paper_money($paper_totals['total']) ?></div>
|
|
<?php } else { ?>
|
|
<div class="itflow-paper-label">Each <?= recurringInvoicePeriodWord($paper_row['recurring_invoice_frequency']) ?></div>
|
|
<div class="itflow-paper-amount"><?= $paper_money($paper_totals['total']) ?></div>
|
|
<?php } ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (trim((string) $paper_row["{$paper_c}_scope"]) !== '') { ?>
|
|
<div class="itflow-paper-scope"><span class="itflow-paper-label me-2">For</span><?= escapeHtml($paper_row["{$paper_c}_scope"]) ?></div>
|
|
<?php } ?>
|
|
|
|
<!-- The lines -->
|
|
<?php if ($paper_edit) { ?>
|
|
<!-- What the add field offers, and the rates a line's tax is picked from -->
|
|
<script type="application/json" data-billing-products><?= getProductsForAutocomplete($GLOBALS['mysqli'], intval($paper_row['client_id'])) ?></script>
|
|
<template data-billing-taxes><?= taxOptionsHtml($paper['taxes'] ?? [], 0) ?></template>
|
|
<?php } ?>
|
|
<div class="table-responsive">
|
|
<table class="table itflow-paper-items align-top mb-0" data-billing-items>
|
|
<thead>
|
|
<tr>
|
|
<?php if ($paper_edit) { ?><th class="itflow-paper-handle d-print-none"></th><?php } ?>
|
|
<th>Item</th>
|
|
<th class="text-end">Qty</th>
|
|
<th class="text-end">Price</th>
|
|
<th class="text-end">Tax</th>
|
|
<th class="text-end">Amount</th>
|
|
<?php if ($paper_edit) { ?><th class="d-print-none"></th><?php } ?>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($paper_line_groups as $paper_frequency => $paper_line_group) {
|
|
if ($paper_groups) { ?>
|
|
<tr class="itflow-paper-group" data-group="<?= $paper_frequency ?>">
|
|
<td colspan="7"><span class="itflow-paper-label"><?= escapeHtml($paper_line_group['label']) ?></span></td>
|
|
</tr>
|
|
<?php }
|
|
foreach ($paper_line_group['items'] as $item) {
|
|
$item_id = intval($item['item_id']);
|
|
$item_tax_label = floatval($item['item_tax']) != 0 || intval($item['item_tax_id'])
|
|
? trim(($item['tax_name'] ?? '') . ' ' . ($item['tax_percent'] !== null ? taxPercentText($item['tax_percent']) . '%' : ''))
|
|
: '';
|
|
$item_tax_html = $item_tax_label !== ''
|
|
? $paper_money($item['item_tax']) . '<div class="small text-body-secondary itflow-paper-tax-label">' . escapeHtml($item_tax_label) . '</div>'
|
|
: '<span class="text-body-secondary">-</span>';
|
|
$item_quantity = billingQuantity($item['item_quantity']); ?>
|
|
<tr id="line-<?= $item_id ?>" data-item-id="<?= $item_id ?>" data-group="<?= $paper_frequency ?>">
|
|
<?php if ($paper_edit) { ?>
|
|
<td class="itflow-paper-handle d-print-none"><span class="drag-handle" title="Drag to reorder"><i class="fas fa-grip-vertical"></i></span></td>
|
|
<?php } ?>
|
|
<td class="itflow-paper-item">
|
|
<div class="fw-semibold"><?= $paper_field('name', $item['item_name'], 'name', escapeHtml($item['item_name']), 'd-block') ?></div>
|
|
<?php if (trim((string) $item['item_description']) !== '') { ?>
|
|
<div class="small text-body-secondary"><?= $paper_field('description', $item['item_description'], 'description', nl2br(escapeHtml($item['item_description'])), 'd-block') ?></div>
|
|
<?php } elseif ($paper_edit) { // shown while the line is being worked on ?>
|
|
<div class="small text-body-secondary itflow-line-blank d-print-none"><?= $paper_field('description', '', 'description', 'Add a description', 'd-block') ?></div>
|
|
<?php } ?>
|
|
<?php if (isset($paper_seats[$item_id])) {
|
|
$seat = $paper_seats[$item_id];
|
|
$seat_source = $paper_seat_sources[$seat['item_seat_source']] ?? ['unit' => 'seat', 'icon' => 'fa-users']; ?>
|
|
<div class="small mt-1 d-print-none">
|
|
<span class="badge rounded-pill bg-primary-subtle text-primary-emphasis fw-normal text-wrap text-start" title="The quantity is counted again for each invoice">
|
|
<i class="fas <?= $seat_source['icon'] ?> me-1"></i>Per <?= $seat_source['unit'] ?> · <?= escapeHtml($seat['seat_label']) ?><?= floatval($seat['item_seat_minimum']) > 0 ? ' · at least ' . billingQuantity($seat['item_seat_minimum']) : '' ?>
|
|
</span>
|
|
<?php if ($seat['seat_count'] === null) { ?>
|
|
<?php if (empty($paper['client'])) { ?><span class="badge rounded-pill text-bg-danger fw-normal">The license is gone - it stays at <?= billingQuantity($item['item_quantity']) ?></span><?php } ?>
|
|
<?php } elseif ($seat['seat_drift']) { ?>
|
|
<span class="badge rounded-pill text-bg-warning fw-normal" title="The next invoice bills the new count">Now <?= billingQuantity($seat['seat_quantity']) ?></span>
|
|
<?php } ?>
|
|
</div>
|
|
<?php } ?>
|
|
</td>
|
|
<?php // A counted quantity isn't typed - the Edit Line window has what it counts ?>
|
|
<td class="text-end text-nowrap"><?= isset($paper_seats[$item_id]) ? $item_quantity : $paper_field('qty', $item_quantity, 'quantity', $item_quantity) ?></td>
|
|
<td class="text-end text-nowrap"><?= $paper_field('price', number_format(floatval($item['item_price']), 2, '.', ''), 'price', $paper_money($item['item_price'])) ?></td>
|
|
<td class="text-end text-nowrap"><?= $paper_field('tax_id', intval($item['item_tax_id']), 'tax', $item_tax_html) ?></td>
|
|
<td class="text-end text-nowrap fw-semibold"><?= $paper_money($item['item_total']) ?></td>
|
|
<?php if ($paper_edit) { ?>
|
|
<td class="text-end d-print-none itflow-paper-actions">
|
|
<div class="dropdown dropstart">
|
|
<button class="btn btn-sm btn-link text-body-secondary" title="Line actions" type="button" data-bs-toggle="dropdown"><i class="fas fa-fw fa-ellipsis-v"></i></button>
|
|
<div class="dropdown-menu">
|
|
<a class="dropdown-item ajax-modal" href="#" data-modal-url="modals/billing/billing_item.php?type=<?= $paper_type ?>&doc_id=<?= $paper_id ?>&item_id=<?= $item_id ?>">
|
|
<i class="fas fa-fw fa-edit text-secondary me-2"></i>Edit
|
|
</a>
|
|
<?php if ($paper_type === 'quote') {
|
|
$item_frequency = quoteFrequency($item['item_frequency'] ?? ''); ?>
|
|
<div class="dropdown-divider"></div>
|
|
<h6 class="dropdown-header">Billed</h6>
|
|
<?php foreach (quoteFrequencies() as $frequency_value => $frequency_label) { ?>
|
|
<button type="button" class="dropdown-item" data-line-set="frequency" data-value="<?= $frequency_value ?>"<?= $item_frequency === $frequency_value ? ' aria-current="true"' : '' ?>>
|
|
<i class="fas fa-fw <?= $item_frequency === $frequency_value ? 'fa-check' : '' ?> text-secondary me-2"></i><?= $frequency_label ?>
|
|
</button>
|
|
<?php } ?>
|
|
<?php } ?>
|
|
<div class="dropdown-divider"></div>
|
|
<a class="dropdown-item text-danger confirm-link" href="<?= escapeHtml($paper_post(['billing_item_delete' => $item_id])) ?>"
|
|
data-confirm-text="<?= escapeHtml($item['item_name']) ?> comes off <?= escapeHtml(billingNumber($paper_type, $paper_row)) ?><?= $paper_type === 'invoice' && intval($item['item_product_id']) ? ' - anything taken from stock goes back' : '' ?>.">
|
|
<i class="fas fa-fw fa-trash me-2"></i>Remove
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<?php } ?>
|
|
</tr>
|
|
<?php }
|
|
} ?>
|
|
<?php if (!$paper_items && !$paper_edit) { ?>
|
|
<tr><td colspan="5" class="text-center text-body-secondary py-4">Nothing on it yet.</td></tr>
|
|
<?php } ?>
|
|
</tbody>
|
|
<?php if ($paper_edit) { ?>
|
|
<tfoot class="d-print-none">
|
|
<tr class="itflow-paper-add" id="add-line">
|
|
<td colspan="7">
|
|
<!-- One field: pick a product and it goes on as a line, or type anything and press Enter -->
|
|
<div class="itflow-paper-add-field">
|
|
<i class="fas fa-plus" aria-hidden="true"></i>
|
|
<input type="text" class="form-control" maxlength="200" autocomplete="off" data-billing-add aria-label="Add a line"
|
|
placeholder="<?= $paper_items ? 'Add a line' : 'First line' ?> - type a product, or anything">
|
|
</div>
|
|
<?php if (!empty($paper['ai'])) { ?>
|
|
<div class="d-flex flex-wrap align-items-center column-gap-2 row-gap-1 mt-2 pt-2 border-top" data-billing-ai>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary ajax-modal" data-modal-size="lg" data-modal-url="modals/billing/billing_ai_writer.php?type=<?= $paper_type ?>&doc_id=<?= $paper_id ?>">
|
|
<i class="fas fa-magic me-1"></i>Write it with AI
|
|
</button>
|
|
<span class="small text-body-secondary">Say what it's for - lines from your catalog, labor at the client's rate.</span>
|
|
</div>
|
|
<?php } ?>
|
|
</td>
|
|
</tr>
|
|
</tfoot>
|
|
<?php } ?>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Notes, and what it all comes to -->
|
|
<div class="itflow-paper-bottom">
|
|
<div class="itflow-paper-notes">
|
|
<?php if (!empty($paper['notes_edit'])) { ?>
|
|
<div class="itflow-paper-label d-flex align-items-center">Notes <span class="ms-2 d-print-none fw-normal text-transform-none" data-note-status-for="billingNotes"></span></div>
|
|
<textarea class="form-control form-control-sm itflow-inline-note d-print-none" rows="3" id="billingNotes"
|
|
placeholder="Payment details, terms, a thank you - shown on the <?= strtolower($paper_title) ?>"
|
|
data-endpoint="<?= $paper_c ?>_set_notes" data-id-field="<?= $paper_t['param'] ?>" data-id="<?= $paper_id ?>"
|
|
data-csrf="<?= escapeHtml($paper['csrf'] ?? '') ?>"><?= escapeHtml($paper_row["{$paper_c}_note"]) ?></textarea>
|
|
<div class="d-none d-print-block small"><?= nl2br(escapeHtml($paper_row["{$paper_c}_note"])) ?></div>
|
|
<?php } elseif (trim((string) $paper_row["{$paper_c}_note"]) !== '') { ?>
|
|
<div class="itflow-paper-label">Notes</div>
|
|
<div class="small"><?= nl2br(escapeHtml($paper_row["{$paper_c}_note"])) ?></div>
|
|
<?php } ?>
|
|
</div>
|
|
<?php if ($paper_groups) { ?>
|
|
<table class="itflow-paper-totals"<?= $paper_live('totals') ?>>
|
|
<?php foreach ($paper_groups as $paper_frequency => $paper_group) {
|
|
if ($paper_frequency === '' && !$paper_group['items'] && $paper_group['discount'] == 0) {
|
|
continue;
|
|
} ?>
|
|
<tr class="itflow-paper-totals-head"><th colspan="2"><?= escapeHtml($paper_group['label']) ?></th></tr>
|
|
<tr><th>Subtotal</th><td><?= $paper_money($paper_group['subtotal']) ?></td></tr>
|
|
<?php if ($paper_group['discount'] != 0) { ?>
|
|
<tr><th>Discount</th><td>-<?= $paper_money($paper_group['discount']) ?></td></tr>
|
|
<?php } ?>
|
|
<?php foreach ($paper_group['taxes'] as $tax_label => $tax_amount) { ?>
|
|
<tr><th><?= escapeHtml($tax_label) ?></th><td><?= $paper_money($tax_amount) ?></td></tr>
|
|
<?php } ?>
|
|
<tr class="itflow-paper-total"><th><?= $paper_frequency === '' ? 'Total up front' : 'Total ' . strtolower(escapeHtml($paper_group['label'])) ?></th><td><?= $paper_money($paper_group['total']) ?></td></tr>
|
|
<?php } ?>
|
|
</table>
|
|
<?php } else { ?>
|
|
<table class="itflow-paper-totals"<?= $paper_live('totals') ?>>
|
|
<tr><th>Subtotal</th><td><?= $paper_money($paper_totals['subtotal']) ?></td></tr>
|
|
<?php if ($paper_totals['discount'] != 0) { ?>
|
|
<tr><th>Discount</th><td>-<?= $paper_money($paper_totals['discount']) ?></td></tr>
|
|
<?php } ?>
|
|
<?php foreach ($paper_totals['taxes'] as $tax_label => $tax_amount) { ?>
|
|
<tr><th><?= escapeHtml($tax_label) ?></th><td><?= $paper_money($tax_amount) ?></td></tr>
|
|
<?php } ?>
|
|
<?php if ($paper_totals['credit'] > 0) { ?>
|
|
<tr><th>Credit</th><td>-<?= $paper_money($paper_totals['credit']) ?></td></tr>
|
|
<?php } ?>
|
|
<tr class="itflow-paper-total"><th>Total</th><td><?= $paper_money($paper_totals['total']) ?></td></tr>
|
|
<?php if ($paper_type === 'invoice' && ($paper['paid'] ?? 0) > 0) { ?>
|
|
<tr class="text-success"><th>Paid</th><td>-<?= $paper_money($paper['paid']) ?></td></tr>
|
|
<tr class="itflow-paper-total"><th>Balance due</th><td><?= $paper_money($paper['balance']) ?></td></tr>
|
|
<?php } ?>
|
|
</table>
|
|
<?php } ?>
|
|
</div>
|
|
|
|
<?php if (trim((string) $paper_footer) !== '') { ?>
|
|
<div class="itflow-paper-footer"><?= nl2br(escapeHtml($paper_footer)) ?></div>
|
|
<?php } ?>
|
|
|
|
</div>
|