Files
itflow/agent/reconcile.php
johnnyq df668d83e6 Accounts Reconcile: reconcile an account against its bank or card statement from the account's menu on Accounts or Transactions - enter the statement's ending date and balance (what is owed, for a card) and notes, starting from where the last statement ended or the opening balance, with lines brought in from bank statements pre-ticked; tick what cleared with the cleared balance and difference updating as you go, each tick saved immediately so a statement can be finished later, pills for money in, money out, not ticked and ticked, search, tick and untick all shown, a bar keeping the difference and Finish in view, and Finish only once the difference is nothing; change the statement, discard it, or add a missing line with New Expense, New Income or New Transfer starting on the account; a history of reconciled statements with ending balance, lines cleared, money in and out and who reconciled it, with Undo on the latest putting it back in progress with its ticks; Transactions marks cleared lines linking to their statement, adds an Uncleared pill and a filter for one statement's lines, the export adds a Cleared column, and Accounts shows where each account is reconciled to; a cleared payment, other income, expense, transfer or reimbursement keeps its amount, date and account and can't be deleted until its statement is undone, which also blocks deleting its invoice or client (including through the API), refunding a cleared Stripe payment, bulk set account and bulk delete on cleared lines, and undoing a cleared reimbursement, with quick looks and edit windows showing where it cleared; the opening balance locks once an account has a reconciled statement and a ticked line moved to another account comes off its statement; reconciling needs write Financial access and undoing full access; new functions/reconcile.php, agent/reconcile.php, agent/post/reconcile.php and js/reconcile.js with ticking through agent/ajax.php; database 3.0.8 adds account_reconciliations and the reconciliation id on payments, revenues and expenses; demo data reconciles Operating Checking and the Business Visa monthly through last month; CHANGELOG updated
2026-10-16 23:12:22 -04:00

428 lines
26 KiB
PHP

<?php
/*
* Reconcile - check an account against its bank or card statement
* (functions/reconcile.php). Enter the statement's ending date and balance,
* then tick each line the statement shows; the difference has to come to
* nothing to finish. Ticks are saved as they are made (agent/ajax.php's
* reconcile_tick, js/reconcile.js), so one can be left and finished later.
* Lines brought in from a bank statement start ticked.
*
* With nothing in progress: where the account is reconciled to, the form to
* start the next statement, and the statements reconciled before - each
* opening the lines it cleared on Transactions, the latest one with Undo.
*
* Takes ?account= - or the account being reconciled, the default account
* payments go into, or the first.
*/
require_once "includes/inc_all.php";
enforceUserPermission('module_financial');
$can_edit = lookupUserPermission('module_financial') >= 2;
$can_undo = lookupUserPermission('module_financial') >= 3;
$types = accountTypes();
$kinds = accountLedgerKinds();
$open_accounts = accountsList(false);
$account_id = intval($_GET['account'] ?? 0);
$account = $account_id ? accountRow($account_id) : null;
$missing_account = $account_id && !$account;
if (!$account) {
$pick = 0;
foreach ($open_accounts as $id => $option) {
if ($option['reconciling_id']) {
$pick = $id;
break;
}
}
if (!$pick && isset($open_accounts[intval($config_default_payment_account)])) {
$pick = intval($config_default_payment_account);
}
if (!$pick && $open_accounts) {
$pick = array_key_first($open_accounts);
}
$account = $pick ? $open_accounts[$pick] : null;
}
$account_id = $account ? intval($account['account_id']) : 0;
$is_card = $account && $account['type'] === 3;
$currency = $account ? (string) $account['account_currency_code'] : (string) $session_company_currency;
$money = function ($amount) use ($currency) {
return escapeHtml(billingMoney($amount, $currency));
};
// A balance as the statement reads it - a card's as what is owed
$statement_money = function ($balance) use ($account, $money) {
return $account ? escapeHtml(reconcileBalanceText($account, $balance)) : $money($balance);
};
$open = $account ? reconcileOpen($account_id) : null;
$last = $account ? reconcileLast($account_id) : null;
$history = $account && !$open ? reconcileHistory($account_id) : [];
$starting = $account ? reconcileStartingBalance($account_id) : 0.0;
// The next statement most likely ends at the end of last month
$suggest = date('Y-m-t', strtotime('first day of last month'));
if ($last && $suggest <= $last['reconciliation_statement_date']) {
$suggest = date('Y-m-d');
}
if ($open) {
$totals = reconcileTotals($open);
$lines = reconcileLines($open, $later);
$statement_date = $open['reconciliation_statement_date'];
} else {
$uncleared = $account ? mysqli_fetch_row(mysqli_query($mysqli, "SELECT COUNT(*), COALESCE(SUM(ledger.txn_amount), 0) FROM (" . accountLedgerSql($account_id) . ") AS ledger WHERE ledger.txn_cleared_id IS NULL")) : [0, 0];
}
$statement_form = function ($row) use ($account, $is_card, $suggest, $last, $starting, $money, $statement_money) {
$date = $row ? $row['reconciliation_statement_date'] : $suggest;
$ending = $row ? floatval($row['reconciliation_ending_balance']) * ($is_card ? -1 : 1) : null;
?>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Statement ends <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-calendar-check"></i></span>
<input type="date" class="form-control" name="statement_date" max="<?= date('Y-m-d') ?>" <?= $last ? 'min="' . escapeHtml(date('Y-m-d', strtotime($last['reconciliation_statement_date'] . ' +1 day'))) . '"' : '' ?> value="<?= escapeHtml($date) ?>" required>
</div>
</div>
<div class="col-md-4">
<label class="form-label"><?= $is_card ? 'Owed on the statement' : 'Ending balance' ?> <strong class="text-danger">*</strong></label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-dollar-sign"></i></span>
<input type="text" class="form-control text-end fw-semibold" inputmode="decimal" pattern="-?[0-9,]*\.?[0-9]{0,2}" name="ending_balance" placeholder="0.00" required
value="<?= $ending === null ? '' : escapeHtml(number_format($ending, 2, '.', '')) ?>">
<span class="input-group-text"><?= escapeHtml($account['account_currency_code']) ?></span>
</div>
<div class="form-text"><?= $is_card ? 'The new balance the statement says you owe' : 'The balance the statement ends on' ?></div>
</div>
<div class="col-md-4">
<label class="form-label">Notes</label>
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-sticky-note"></i></span>
<input type="text" class="form-control" name="notes" maxlength="250" placeholder="The statement's number, or anything to remember" value="<?= escapeHtml($row['reconciliation_notes'] ?? '') ?>">
</div>
</div>
</div>
<div class="form-text mt-2">
<i class="fas fa-info-circle me-1"></i>It starts from <?= $statement_money($starting) ?> -
<?= $last ? 'where the statement to ' . escapeHtml(date('M j, Y', strtotime($last['reconciliation_statement_date']))) . ' ended' : "the account's opening balance, as this is its first statement" ?>.
</div>
<?php
};
?>
<?php if ($missing_account) { ?>
<div class="alert alert-warning"><i class="fas fa-exclamation-triangle me-2"></i>That account no longer exists.</div>
<?php } ?>
<?php if (!$account) { ?>
<div class="card">
<div class="itflow-asset-empty">
<i class="fas fa-piggy-bank fa-2x mb-2 opacity-50"></i>
<div>Add an account to reconcile first.</div>
<a class="btn btn-sm btn-outline-primary mt-2" href="accounts.php">Accounts</a>
</div>
</div>
<?php require_once "../includes/footer.php";
return;
} ?>
<?php if ($open) { ?>
<!-- Where the statement being reconciled stands - updated as lines are ticked -->
<div class="row g-3 mb-3" id="reconcileTotals">
<div class="col-sm-6 col-xl-3">
<div class="itflow-billing-stat">
<div class="itflow-billing-stat-label"><i class="fas fa-flag me-1"></i>Starting from</div>
<div class="itflow-billing-stat-value"><?= $statement_money($totals['starting']) ?></div>
<div class="itflow-billing-stat-note"><?= $last ? 'Reconciled to ' . escapeHtml(date('M j, Y', strtotime($last['reconciliation_statement_date']))) : 'The opening balance' ?></div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="itflow-billing-stat">
<div class="itflow-billing-stat-label"><i class="fas fa-file-invoice me-1"></i>The statement</div>
<div class="itflow-billing-stat-value"><?= $statement_money($totals['ending']) ?></div>
<div class="itflow-billing-stat-note">Ending <?= escapeHtml(date('M j, Y', strtotime($statement_date))) ?></div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="itflow-billing-stat">
<div class="itflow-billing-stat-label"><i class="fas fa-check-double me-1"></i>Cleared</div>
<div class="itflow-billing-stat-value" data-reconcile-cleared><?= $statement_money($totals['cleared']) ?></div>
<div class="itflow-billing-stat-note" data-reconcile-ticked><?= $totals['count'] ?> ticked - <?= $money($totals['in']) ?> in, <?= $money($totals['out']) ?> out</div>
</div>
</div>
<div class="col-sm-6 col-xl-3">
<div class="itflow-billing-stat" data-reconcile-difference-card>
<div class="itflow-billing-stat-label"><i class="fas fa-balance-scale me-1"></i>Difference</div>
<div class="itflow-billing-stat-value" data-reconcile-difference><?= $money($totals['difference']) ?></div>
<div class="itflow-billing-stat-note" data-reconcile-difference-note></div>
</div>
</div>
</div>
<div class="card" id="reconcile">
<div class="card-header bg-dark py-2 d-flex align-items-center gap-2">
<h3 class="card-title text-truncate flex-grow-1 min-w-0 mb-0">
<i class="fas fa-fw fa-tasks me-2"></i>Reconcile <?= escapeHtml($account['account_name']) ?>
<small class="text-white-50 ms-2 d-none d-md-inline">statement to <?= escapeHtml(date('M j, Y', strtotime($statement_date))) ?></small>
</h3>
<div class="card-tools flex-shrink-0 ms-0">
<?php if ($can_edit) { ?>
<form class="d-inline" id="reconcileFinish" action="post.php" method="post">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="reconciliation_id" value="<?= intval($open['reconciliation_id']) ?>">
<div class="btn-group">
<button type="submit" name="reconcile_finish" class="btn btn-success" data-reconcile-finish <?= abs($totals['difference']) >= 0.005 ? 'disabled' : '' ?>
title="Once the difference is nothing"><i class="fas fa-check me-2"></i>Finish</button>
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-label="More"></button>
<div class="dropdown-menu dropdown-menu-end">
<a class="dropdown-item" href="#reconcileStatement" data-bs-toggle="collapse"><i class="fa fa-fw fa-edit text-secondary me-2"></i>Change the statement</a>
<div class="dropdown-divider"></div>
<h6 class="dropdown-header">Add what the statement has</h6>
<a class="dropdown-item ajax-modal" href="#" data-modal-size="lg" data-modal-url="modals/expense/expense_add.php?account_id=<?= $account_id ?>"><i class="fa fa-fw fa-cart-plus text-secondary me-2"></i>New Expense</a>
<a class="dropdown-item ajax-modal" href="#" data-modal-size="lg" data-modal-url="modals/revenue/revenue_add.php?account_id=<?= $account_id ?>"><i class="fa fa-fw fa-hand-holding-usd text-secondary me-2"></i>New Income</a>
<a class="dropdown-item ajax-modal" href="#" data-modal-size="lg" data-modal-url="modals/transfer/transfer_add.php?<?= $is_card ? 'to' : 'from' ?>=<?= $account_id ?>"><i class="fa fa-fw fa-exchange-alt text-secondary me-2"></i>New Transfer</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="transactions.php?account=<?= $account_id ?>"><i class="fa fa-fw fa-list-alt text-secondary me-2"></i>Transactions</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item text-danger confirm-link" href="post.php?reconcile_discard=<?= intval($open['reconciliation_id']) ?>&amp;csrf_token=<?= $_SESSION['csrf_token'] ?>"
data-confirm-title="Discard this reconciliation?" data-confirm-text="What is ticked comes off, and nothing is marked cleared." data-confirm-button="Discard"><i class="fa fa-fw fa-trash-alt me-2"></i>Discard</a>
</div>
</div>
</form>
<?php } ?>
</div>
</div>
<?php if ($can_edit) { ?>
<div class="collapse" id="reconcileStatement">
<form class="card-header py-3" action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="reconciliation_id" value="<?= intval($open['reconciliation_id']) ?>">
<?php $statement_form($open); ?>
<div class="mt-3">
<button type="submit" name="reconcile_update" class="btn btn-primary btn-sm"><i class="fas fa-check me-2"></i>Save the statement</button>
<button type="button" class="btn btn-light btn-sm" data-bs-toggle="collapse" data-bs-target="#reconcileStatement"><i class="fas fa-times me-2"></i>Cancel</button>
</div>
</form>
</div>
<?php } ?>
<div class="card-header py-3">
<div class="row g-2 align-items-center">
<div class="col-lg-4 col-md-6">
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-search"></i></span>
<input type="search" class="form-control" placeholder="Find a line - who, reference or amount" data-reconcile-search aria-label="Find a line">
</div>
</div>
<?php if ($can_edit) { ?>
<div class="col-lg-8 col-md-6 text-md-end">
<button type="button" class="btn btn-outline-secondary btn-sm" data-reconcile-all="1"><i class="far fa-check-square me-1"></i>Tick all shown</button>
<button type="button" class="btn btn-outline-secondary btn-sm" data-reconcile-all="0"><i class="far fa-square me-1"></i>Untick all shown</button>
</div>
<?php } ?>
</div>
<nav class="nav nav-pills itflow-asset-tabs mt-2 flex-nowrap overflow-auto" data-reconcile-pills>
<?php foreach (['' => ['All', ''], 'in' => ['Money in', 'fa-arrow-down'], 'out' => ['Money out', 'fa-arrow-up'], 'unticked' => ['Not ticked', 'fa-hourglass-half'], 'ticked' => ['Ticked', 'fa-check']] as $key => [$label, $icon]) { ?>
<a class="nav-link text-nowrap <?= $key === '' ? 'active' : '' ?>" href="#" data-reconcile-show="<?= $key ?>">
<?php if ($icon) { ?><i class="fas fa-fw <?= $icon ?> me-1"></i><?php } ?><?= $label ?>
<span class="badge rounded-pill ms-1" data-reconcile-count="<?= $key ?>">0</span>
</a>
<?php } ?>
</nav>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0 itflow-billing-table itflow-reconcile-table">
<thead class="text-nowrap table-light <?= $lines ? '' : 'd-none' ?>">
<tr>
<th class="text-center" style="width: 3rem;"><span class="visually-hidden">Cleared</span><i class="fas fa-check text-body-secondary" title="Cleared on this statement"></i></th>
<th>Date</th>
<th>Who or what</th>
<th class="text-end">In</th>
<th class="text-end">Out</th>
</tr>
</thead>
<tbody>
<?php foreach ($lines as $line) {
$kind = $line['txn_kind'];
$info = $kinds[$kind];
$amount = round(floatval($line['txn_amount']), 2);
[$what, $description] = accountLedgerWhat($line);
$meta = array_filter([$info['label'], (string) $line['txn_method'], $line['txn_reference'] ? '#' . $line['txn_reference'] : '']);
$search = mb_strtolower(implode(' ', [$what, $description, (string) $line['txn_reference'], (string) $line['client_name'], number_format(abs($amount), 2, '.', ''), number_format(abs($amount), 2)]));
?>
<tr data-reconcile-line="<?= escapeHtml($line['txn_line']) ?>" data-amount="<?= escapeHtml(number_format($amount, 2, '.', '')) ?>" data-search="<?= escapeHtml($search) ?>" class="<?= $line['ticked'] ? 'itflow-reconcile-ticked' : '' ?>">
<td class="text-center">
<input class="form-check-input" type="checkbox" data-reconcile-tick <?= $line['ticked'] ? 'checked' : '' ?> <?= $can_edit ? '' : 'disabled' ?> aria-label="Cleared">
</td>
<td class="text-nowrap"><a class="ajax-modal" href="#" data-modal-size="lg" data-modal-url="<?= escapeHtml(accountLedgerModal($line)) ?>"><?= escapeHtml($line['txn_date']) ?></a></td>
<td class="itflow-ledger-what">
<div class="text-truncate"><i class="fas fa-fw <?= $info['icon'] ?> text-body-secondary me-1"></i><?= escapeHtml($what) ?><?php if ($description !== '') { ?><span class="text-body-secondary"> - <?= escapeHtml($description) ?></span><?php } ?></div>
<div class="small text-body-secondary text-truncate">
<?= escapeHtml(implode(' · ', $meta)) ?>
<?php if (intval($line['txn_imported'])) { ?><span class="ms-1" title="Brought in from a bank or card statement"><i class="fas fa-file-import"></i></span><?php } ?>
</div>
</td>
<td class="text-end text-nowrap itflow-money text-success-emphasis fw-semibold"><?= $amount > 0 ? $money($amount) : '' ?></td>
<td class="text-end text-nowrap itflow-money fw-semibold"><?= $amount < 0 ? $money(-$amount) : '' ?></td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php if (!$lines) { ?>
<div class="itflow-asset-empty">
<i class="fas fa-check-double fa-2x mb-2 opacity-50"></i>
<div>Nothing on <?= escapeHtml($account['account_name']) ?> up to <?= escapeHtml(date('M j, Y', strtotime($statement_date))) ?> is left to clear.</div>
</div>
<?php } ?>
<!-- Stays in view down a long statement: where it stands, and Finish -->
<div class="card-footer itflow-reconcile-bar d-flex flex-wrap gap-2 align-items-center">
<div class="small text-body-secondary me-auto">
<i class="fas fa-save me-1"></i>Ticks are saved as you go - leave it and come back to finish any time.
<?php if ($later) { ?>
<div><?= $later ?> line<?= $later === 1 ? '' : 's' ?> after <?= escapeHtml(date('M j, Y', strtotime($statement_date))) ?> wait for the next statement.</div>
<?php } ?>
</div>
<div class="text-nowrap">
<span class="small text-body-secondary me-1">Difference</span>
<span class="fw-semibold itflow-money" data-reconcile-difference><?= $money($totals['difference']) ?></span>
</div>
<?php if ($can_edit) { ?>
<button type="submit" form="reconcileFinish" name="reconcile_finish" class="btn btn-success btn-sm" data-reconcile-finish <?= abs($totals['difference']) >= 0.005 ? 'disabled' : '' ?>
title="Once the difference is nothing"><i class="fas fa-check me-2"></i>Finish</button>
<?php } ?>
</div>
</div>
<script src="/js/reconcile.js"></script>
<script>
itflowReconcile({
id: <?= intval($open['reconciliation_id']) ?>,
csrf: <?= json_encode($_SESSION['csrf_token']) ?>,
starting: <?= json_encode($totals['starting']) ?>,
ending: <?= json_encode($totals['ending']) ?>,
card: <?= $is_card ? 'true' : 'false' ?>,
currency: <?= json_encode($currency) ?>,
locale: <?= json_encode(str_replace('_', '-', (string) $session_company_locale)) ?>
});
</script>
<?php } else { ?>
<!-- Where the account is reconciled to -->
<div class="row g-3 mb-3">
<div class="col-sm-6 col-xl-4">
<a class="itflow-billing-stat" href="transactions.php?account=<?= $account_id ?>">
<div class="itflow-billing-stat-label"><i class="fas <?= $types[$account['type']]['icon'] ?> me-1"></i><?= $is_card && $account['balance'] < 0 ? 'Owed now' : 'Holds now' ?></div>
<div class="itflow-billing-stat-value"><?= $statement_money($account['balance']) ?></div>
<div class="itflow-billing-stat-note"><?= escapeHtml($types[$account['type']]['label']) ?> &middot; <?= escapeHtml($account['account_currency_code']) ?></div>
</a>
</div>
<div class="col-sm-6 col-xl-4">
<div class="itflow-billing-stat">
<div class="itflow-billing-stat-label"><i class="fas fa-check-double me-1"></i>Reconciled to</div>
<div class="itflow-billing-stat-value"><?= $last ? escapeHtml(date('M j, Y', strtotime($last['reconciliation_statement_date']))) : 'Never' ?></div>
<div class="itflow-billing-stat-note"><?= $last ? 'Ending at ' . $statement_money($last['reconciliation_ending_balance']) : 'Its first statement starts from the opening balance' ?></div>
</div>
</div>
<div class="col-sm-6 col-xl-4">
<a class="itflow-billing-stat" href="transactions.php?account=<?= $account_id ?>&amp;show=uncleared">
<div class="itflow-billing-stat-label"><i class="fas fa-hourglass-half me-1"></i>Not cleared yet</div>
<div class="itflow-billing-stat-value"><?= intval($uncleared[0]) ?> line<?= intval($uncleared[0]) === 1 ? '' : 's' ?></div>
<div class="itflow-billing-stat-note"><?= intval($uncleared[0]) ? $money($uncleared[1]) . ' net, not on a reconciled statement' : 'Everything on it is reconciled' ?></div>
</a>
</div>
</div>
<div class="card">
<div class="card-header bg-dark py-2 d-flex align-items-center gap-2">
<h3 class="card-title text-truncate flex-grow-1 min-w-0 mb-0"><i class="fas fa-fw fa-tasks me-2"></i>Reconcile <?= escapeHtml($account['account_name']) ?></h3>
<div class="card-tools flex-shrink-0 ms-0">
<a class="btn btn-light" href="transactions.php?account=<?= $account_id ?>"><i class="fas fa-list-alt me-2"></i>Transactions</a>
</div>
</div>
<div class="card-header py-3">
<div class="row g-2">
<div class="col-lg-4 col-md-6">
<div class="input-group">
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
<select class="form-select select2" aria-label="Account" onchange="window.location.href = 'reconcile.php?account=' + this.value">
<?php foreach ($open_accounts + ($account['account_archived_at'] ? [$account_id => $account] : []) as $id => $option) { ?>
<option value="<?= $id ?>" <?= $account_id === $id ? 'selected' : '' ?> data-icon="fas <?= $types[$option['type']]['icon'] ?>"
data-description="<?= escapeHtml($option['reconciling_id'] ? 'Being reconciled' : ($option['reconciled_to'] ? 'Reconciled to ' . date('M j, Y', strtotime($option['reconciled_to'])) : 'Never reconciled')) ?>"><?= escapeHtml($option['account_name']) ?></option>
<?php } ?>
</select>
</div>
</div>
</div>
</div>
<?php if ($can_edit && !$account['account_archived_at']) { ?>
<form class="card-body border-bottom" action="post.php" method="post" autocomplete="off">
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
<input type="hidden" name="account_id" value="<?= $account_id ?>">
<h6 class="fw-semibold mb-3">The next statement</h6>
<?php $statement_form(null); ?>
<button type="submit" name="reconcile_start" class="btn btn-primary mt-3"><i class="fas fa-play me-2"></i>Start reconciling</button>
</form>
<?php } ?>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0 itflow-billing-table">
<thead class="text-nowrap table-light <?= $history ? '' : 'd-none' ?>">
<tr>
<th>Statement to</th>
<th class="text-end"><?= $is_card ? 'Owed' : 'Ending balance' ?></th>
<th>Cleared</th>
<th class="text-end">In</th>
<th class="text-end">Out</th>
<th>Reconciled</th>
<th></th>
</tr>
</thead>
<tbody>
<?php foreach ($history as $i => $row) {
$t = $row['totals']; ?>
<tr>
<td class="text-nowrap fw-semibold"><?= escapeHtml(date('M j, Y', strtotime($row['reconciliation_statement_date']))) ?>
<?php if ($row['reconciliation_notes'] !== null && $row['reconciliation_notes'] !== '') { ?><div class="small text-body-secondary fw-normal text-truncate" style="max-width: 16rem;"><?= escapeHtml($row['reconciliation_notes']) ?></div><?php } ?>
</td>
<td class="text-end text-nowrap itflow-money fw-semibold"><?= $statement_money($row['reconciliation_ending_balance']) ?></td>
<td class="text-nowrap"><a href="transactions.php?account=<?= $account_id ?>&amp;reconciliation=<?= intval($row['reconciliation_id']) ?>" title="The lines it cleared"><?= $t['count'] ?> line<?= $t['count'] === 1 ? '' : 's' ?></a>
<div class="small text-body-secondary">from <?= $statement_money($t['starting']) ?></div>
</td>
<td class="text-end text-nowrap itflow-money text-success-emphasis"><?= $money($t['in']) ?></td>
<td class="text-end text-nowrap itflow-money"><?= $money($t['out']) ?></td>
<td class="small text-nowrap">
<?= escapeHtml($row['finished_by_name'] ?? 'Someone since removed') ?>
<div class="text-body-secondary" title="<?= escapeHtml($row['reconciliation_finished_at']) ?>"><?= escapeHtml(timeAgo($row['reconciliation_finished_at'])) ?></div>
</td>
<td class="text-end">
<?php if ($i === 0 && $can_undo) { ?>
<a class="btn btn-sm btn-outline-danger confirm-link" href="post.php?reconcile_undo=<?= intval($row['reconciliation_id']) ?>&amp;csrf_token=<?= $_SESSION['csrf_token'] ?>"
data-confirm-title="Undo this reconciliation?" data-confirm-text="It goes back to being reconciled, with what was ticked - its lines can change again until it is finished." data-confirm-button="Undo"
title="Back to being reconciled"><i class="fas fa-undo me-1"></i>Undo</a>
<?php } ?>
</td>
</tr>
<?php } ?>
</tbody>
</table>
</div>
<?php if (!$history) { ?>
<div class="itflow-asset-empty">
<i class="fas fa-tasks fa-2x mb-2 opacity-50"></i>
<div><?= escapeHtml($account['account_name']) ?> hasn't been reconciled yet.</div>
</div>
<?php } ?>
</div>
<?php } ?>
<?php require_once "../includes/footer.php";