Files
itflow/agent/includes/inc_client_top_head.php
johnnyq afaaa5fc32 Taxes stage 1: Admin > Taxes rebuilt (renamed from Tax Rates) listing each rate with its whole percent, parts, description, registration number and usage (invoices, quotes, recurring invoices, products, clients), Current and Archived tabs showing what replaced a rate, Restore, Make default, and Delete only for a rate never used; rates made of parts (state and county, GST and QST) with parts charged on top of the ones above, the whole rate shown as typed, four decimal places and a registration number per rate or part printed with the company details on invoices, quotes and PDFs; changing the percent or parts of a rate on a sent invoice or quote makes a new rate of the same name and archives the old one, so billed lines keep the old rate while products, client rates, the default, recurring invoices and optionally drafts move to the new one, and archiving offers to move whatever still uses it; How lines are taxed settings for the default rate, taxing billed time, trips and expenses, and discounts taken off before tax (on by default, shared across lines by amount, paid, cancelled and answered documents never recalculated); clients get their own tax rate and tax exempt with certificate number and expiry on the Billing tab, new lines defaulting to the client's rate then the product's then the default and to no tax for exempt clients, exempt badge on the client header and overview, a warning with Take the tax off on open documents that still carry taxed lines for an exempt client, and Tax exempt with the certificate under the client on untaxed documents; totals, PDFs, guest and portal views show tax by part; Unbilled Time can bill at each client's usual rate and the ticket, Add Tickets, Add Expenses and Add Trips windows start on it; API client fields for rate and exemption and tax_id default on invoice item create; Tax Collected shows exact percents; demo taxes get parts and registration numbers, demo clients their rates and two nonprofits exemptions; database 3.1.1 makes tax_percent decimal(9,4) and adds tax description, registration and replaced-by, tax_components, client rate and exemption columns and the tax settings; new functions/tax.php and js/tax_form.js; changelog updated
2026-10-17 13:00:54 -04:00

129 lines
6.9 KiB
PHP

<?php
/*
* The client header at the top of every client page except the overview,
* which draws its own (same tags, same actions menu).
*
* One card: the client's initials, name with its type under it, tags, open
* tickets and any balance due, and the actions menu. Clicking the name opens
* a panel with the same primary contact, location and details as the
* overview's header (includes/client_details.php).
*
* Whether the panel starts open is remembered in a cookie rather than
* localStorage precisely so it can be answered HERE, server side - the panel
* and its chevron then render in the right state on the first paint instead of
* flashing shut and springing open once JS has run. Closed until opened.
*/
$show_add_credit = 0; // Remove once credits is added hides the button
$client_header_open = ($_COOKIE['client_header_open'] ?? '0') === '1';
if (empty($client_top_head_skip)) {
$client_head_edit = lookupUserPermission("module_client") >= 2;
$client_head_billing = lookupUserPermission("module_financial") >= 1 && $config_module_enable_accounting == 1;
$client_head_support = lookupUserPermission("module_support") >= 1 && $config_module_enable_ticketing == 1;
$client_head_invoices = lookupUserPermission("module_sales") >= 1 && $config_module_enable_accounting == 1;
$client_head_glance_tickets = $client_head_support && $num_active_tickets > 0;
$client_head_glance_due = $client_head_billing && $balance > 0;
$client_head_money = function ($amount) use ($currency_format, $client_currency_code, $session_company_currency) {
return numfmt_format_currency($currency_format, floatval($amount), $client_currency_code ?: $session_company_currency);
};
?>
<div class="card mb-3 d-print-none itflow-client-head">
<div class="card-body d-flex align-items-start align-items-md-center gap-2 px-3 py-2">
<div class="d-flex flex-wrap align-items-center column-gap-2 row-gap-2 row-gap-md-1 flex-grow-1 itflow-client-head-main">
<a href="#" class="itflow-client-head-toggle client-header-toggle<?= $client_header_open ? '' : ' collapsed' ?>" data-bs-toggle="collapse" data-bs-target="#clientHeader" aria-controls="clientHeader" aria-expanded="<?= $client_header_open ? 'true' : 'false' ?>" title="Client ID: <?= $client_id ?>">
<i class="fas fa-fw fa-chevron-<?= $client_header_open ? 'down' : 'right' ?> client-header-chevron" aria-hidden="true"></i>
<span class="itflow-client-avatar itflow-client-avatar-sm text-bg-<?= $client_initials_tone ?>" aria-hidden="true"><?= $client_initials ?></span>
<span class="itflow-client-head-title">
<span class="itflow-client-head-name"><?= $client_name ?></span>
<?php if ($client_type) { ?><span class="itflow-client-head-type"><?= $client_type ?></span><?php } ?>
</span>
</a>
<?php if ($client_is_lead) { ?><span class="badge rounded-pill text-bg-info fw-normal">Lead</span><?php } ?>
<?php if ($client_archived_at) { ?><span class="badge rounded-pill text-bg-secondary fw-normal">Archived</span><?php } ?>
<?php if (!empty($config_module_enable_accounting) && ($client_head_tax = taxClient($client_id))['exempt']) { ?>
<span class="badge rounded-pill fw-normal <?= $client_head_tax['expired'] ? 'bg-warning-subtle text-warning-emphasis' : 'bg-success-subtle text-success-emphasis' ?>" title="<?= escapeHtml(taxExemptText($client_head_tax)) ?>"><?= $client_head_tax['expired'] ? 'Tax exempt - certificate expired' : 'Tax exempt' ?></span>
<?php } ?>
<?= tagPills('client', $client_id, $client_tags) ?>
<?php /* Open tickets and balance due, always in sight */ ?>
<?php if ($client_head_glance_tickets || $client_head_glance_due) { ?>
<div class="itflow-client-head-glance d-flex align-items-center gap-3 ms-md-auto">
<?php if ($client_head_glance_tickets) { ?>
<a class="text-body-secondary" href="tickets.php?client_id=<?= $client_id ?>"><i class="fas fa-fw fa-life-ring me-1" aria-hidden="true"></i><?= number_format($num_active_tickets) ?> open</a>
<?php } ?>
<?php if ($client_head_glance_due) { ?>
<?php if ($client_head_invoices) { ?>
<a class="text-danger-emphasis fw-semibold" href="invoices.php?client_id=<?= $client_id ?>&status=Unpaid"><?= $client_head_money($balance) ?> due</a>
<?php } else { ?>
<span class="text-danger-emphasis fw-semibold"><?= $client_head_money($balance) ?> due</span>
<?php } ?>
<?php } ?>
</div>
<?php } ?>
</div>
<?php if ($client_head_edit) { ?>
<div class="dropdown dropstart flex-shrink-0">
<button class="btn btn-outline-secondary btn-sm" title="More actions" type="button" data-bs-toggle="dropdown">
<i class="fas fa-fw fa-ellipsis-v"></i>
</button>
<?php require 'includes/client_actions_menu.php'; ?>
</div>
<?php } ?>
</div>
<div class="collapse<?= $client_header_open ? ' show' : '' ?>" id="clientHeader">
<div class="card-body border-top px-3 py-3">
<div class="row g-3">
<?php require 'includes/client_details.php'; ?>
</div>
</div>
</div>
</div>
<script>
(function () {
/*
* Remember whether the client header is open so it stays that way while you move
* around the client area. The cookie is what the PHP at the top of this file reads
* on the next request. The chevron is swapped by CLASS rather than rotated in CSS,
* so the direction cannot go stale behind a cached stylesheet.
*
* No readiness wrapper: these are document-level listeners for events that cannot
* fire until both Bootstrap and the markup exist, so registration order is moot.
*/
function syncClientHeader(open) {
document.cookie = 'client_header_open=' + (open ? '1' : '0') +
';path=/;max-age=31536000;samesite=lax' +
(location.protocol === 'https:' ? ';secure' : '');
document.querySelectorAll('.client-header-chevron').forEach(function (icon) {
icon.classList.toggle('fa-chevron-down', open);
icon.classList.toggle('fa-chevron-right', !open);
});
}
document.addEventListener('shown.bs.collapse', function (e) {
if (e.target.id === 'clientHeader') {
syncClientHeader(true);
}
});
document.addEventListener('hidden.bs.collapse', function (e) {
if (e.target.id === 'clientHeader') {
syncClientHeader(false);
}
});
})();
</script>
<?php } ?>
<?php
// require_once "modals/client/client_credit_add.php"; --Credit Not Ready 2025-08-27
require_once "modals/client/client_delete.php";