mirror of
https://github.com/itflow-org/itflow
synced 2026-11-11 07:01:16 +00:00
532 lines
35 KiB
PHP
532 lines
35 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.
|
|
* An account's first statement can start from the bank's balance the day it
|
|
* starts, bringing everything before it forward as reconciled; where ITFlow
|
|
* was off the bank that day, the opening balance can be corrected to match.
|
|
*
|
|
* 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;
|
|
// Brought forward from the bank's balance - and how far ITFlow was from it that day
|
|
$opening = $account ? reconcileOpening($account_id) : null;
|
|
$opening_totals = $opening ? reconcileTotals($opening) : null;
|
|
$opening_gap = $opening ? round($opening_totals['difference'], 2) : 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, false) . ") AS ledger WHERE ledger.txn_cleared_id IS NULL")) : [0, 0];
|
|
}
|
|
|
|
$statement_form = function ($row) use ($mysqli, $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;
|
|
$first = !$row && !$last;
|
|
if ($first) {
|
|
// An account with history before the statement most likely starts from the bank's balance
|
|
$start = date('Y-m-01', strtotime($date));
|
|
$account_id = intval($account['account_id']);
|
|
$earlier = mysqli_fetch_row(mysqli_query($mysqli, "SELECT EXISTS (SELECT 1 FROM payments WHERE payment_account_id = $account_id AND payment_date < '$start')
|
|
OR EXISTS (SELECT 1 FROM revenues WHERE revenue_account_id = $account_id AND revenue_date < '$start')
|
|
OR EXISTS (SELECT 1 FROM expenses WHERE expense_account_id = $account_id AND expense_date < '$start')"))[0];
|
|
$from_statement = intval($earlier) === 1;
|
|
?>
|
|
<div class="mb-3" data-reconcile-start>
|
|
<div class="form-label">Its first statement starts from</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" name="start_from" value="statement" id="reconcileStartStatement" <?= $from_statement ? 'checked' : '' ?>>
|
|
<label class="form-check-label" for="reconcileStartStatement">The bank's balance the day the statement starts <span class="text-body-secondary">- everything on <?= escapeHtml($account['account_name']) ?> before that day counts as reconciled</span></label>
|
|
</div>
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="radio" name="start_from" value="opening" id="reconcileStartOpening" <?= $from_statement ? '' : 'checked' ?>>
|
|
<label class="form-check-label" for="reconcileStartOpening">The account's opening balance, <?= $statement_money($account['opening_balance']) ?> <span class="text-body-secondary">- tick everything since it opened</span></label>
|
|
</div>
|
|
</div>
|
|
<div class="row g-3 mb-3" data-reconcile-start-fields <?= $from_statement ? '' : 'hidden' ?>>
|
|
<div class="col-md-4">
|
|
<label class="form-label">Statement starts <strong class="text-danger">*</strong></label>
|
|
<div class="input-group">
|
|
<span class="input-group-text"><i class="fa fa-fw fa-calendar"></i></span>
|
|
<input type="date" class="form-control" name="start_date" max="<?= date('Y-m-d') ?>" value="<?= escapeHtml($start) ?>" required <?= $from_statement ? '' : 'disabled' ?>>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<label class="form-label"><?= $is_card ? 'Owed when it starts' : 'Starting 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="start_balance" placeholder="0.00" required <?= $from_statement ? '' : 'disabled' ?>>
|
|
<span class="input-group-text"><?= escapeHtml($account['account_currency_code']) ?></span>
|
|
</div>
|
|
<div class="form-text"><?= $is_card ? 'The previous balance the statement says you owed' : 'The beginning balance on the statement' ?></div>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
(function () {
|
|
var start = document.querySelector('[data-reconcile-start]');
|
|
var fields = document.querySelector('[data-reconcile-start-fields]');
|
|
if (!start || !fields || start.dataset.wired) {
|
|
return;
|
|
}
|
|
start.dataset.wired = '1';
|
|
start.addEventListener('change', function () {
|
|
var fromStatement = start.querySelector('input[value="statement"]').checked;
|
|
fields.hidden = !fromStatement;
|
|
fields.querySelectorAll('input').forEach(function (input) {
|
|
input.disabled = !fromStatement;
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
<?php
|
|
}
|
|
?>
|
|
<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>
|
|
<?php if (!$first) { ?>
|
|
<div class="form-text mt-2">
|
|
<i class="fas fa-info-circle me-1"></i>It starts from <?= $statement_money($starting) ?> -
|
|
<?= $last && $last['opening'] ? "the bank's balance when " . escapeHtml(date('M j, Y', strtotime($last['reconciliation_statement_date'] . ' +1 day'))) . ' started'
|
|
: ($last ? 'where the statement to ' . escapeHtml(date('M j, Y', strtotime($last['reconciliation_statement_date']))) . ' ended' : "the account's opening balance") ?>.
|
|
</div>
|
|
<?php } ?>
|
|
<?php
|
|
};
|
|
|
|
// Where ITFlow was off the bank on the day it was brought forward - correct the opening balance, or leave it
|
|
$gap_notice = function () use ($opening, $opening_totals, $opening_gap, $account, $can_edit, $statement_money, $money) {
|
|
if (!$opening || abs($opening_gap) < 0.005 || abs(floatval($opening['reconciliation_adjustment'])) >= 0.005) {
|
|
return;
|
|
}
|
|
$day = date('M j, Y', strtotime($opening['reconciliation_statement_date']));
|
|
?>
|
|
<div class="alert alert-warning d-flex flex-wrap gap-2 align-items-center">
|
|
<i class="fas fa-exclamation-triangle"></i>
|
|
<span class="me-auto">On <?= escapeHtml($day) ?> ITFlow had <?= escapeHtml($account['account_name']) ?> at <?= $statement_money($opening_totals['cleared']) ?> and the bank at <?= $statement_money($opening_totals['ending']) ?>
|
|
- <?= $money(abs($opening_gap)) ?> apart. Correct the opening balance by that so ITFlow matches the bank from then on, or leave it - the statements reconcile from the bank's balance either way.</span>
|
|
<?php if ($can_edit) { ?>
|
|
<a class="btn btn-sm btn-warning confirm-link" href="post.php?reconcile_correct_opening=<?= intval($opening['reconciliation_id']) ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"
|
|
data-confirm-title="Correct the opening balance?" data-confirm-text="<?= escapeHtml($account['account_name']) ?>'s opening balance moves by <?= $money($opening_gap) ?>, to <?= $money(floatval($account['opening_balance']) + $opening_gap) ?>, and every balance after it with it. Undoing the brought forward reconciliation puts it back."
|
|
data-confirm-button="Correct it"><i class="fas fa-wrench me-1"></i>Correct the opening balance</a>
|
|
<?php } ?>
|
|
</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) { ?>
|
|
|
|
<?php $gap_notice(); ?>
|
|
|
|
<!-- 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 && $last['opening'] ? "The bank's, when " . escapeHtml(date('M j, Y', strtotime($last['reconciliation_statement_date'] . ' +1 day'))) . ' started'
|
|
: ($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']) ?>&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) {
|
|
$line = accountLedgerVisible($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"><?php if ($line['hidden']) { ?><span title="For a client you cannot open"><?= escapeHtml($line['txn_date']) ?></span><?php } else { ?><a class="ajax-modal" href="#" data-modal-size="lg" data-modal-url="<?= escapeHtml(accountLedgerModal($line)) ?>"><?= escapeHtml($line['txn_date']) ?></a><?php } ?></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="<?= staticFileUrl('/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 { ?>
|
|
|
|
<?php $gap_notice(); ?>
|
|
|
|
<!-- 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']) ?> · <?= 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 && $last['opening'] ? "Brought forward at the bank's " . $statement_money($last['reconciliation_ending_balance'])
|
|
: ($last ? 'Ending at ' . $statement_money($last['reconciliation_ending_balance']) : 'Its first statement starts from the bank\'s balance or 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 ?>&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['opening']) { ?>
|
|
<div class="small text-body-secondary fw-normal"><i class="fas fa-angle-double-right me-1"></i>Brought forward - everything before <?= escapeHtml(date('M j, Y', strtotime($row['reconciliation_statement_date'] . ' +1 day'))) ?></div>
|
|
<?php } elseif ($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']) ?>
|
|
<?php if ($row['opening'] && abs(floatval($row['reconciliation_adjustment'])) >= 0.005) { ?>
|
|
<div class="small text-body-secondary fw-normal">opening balance corrected by <?= $money($row['reconciliation_adjustment']) ?></div>
|
|
<?php } elseif ($row['opening'] && abs($t['difference']) >= 0.005) { ?>
|
|
<div class="small text-warning-emphasis fw-normal"><?= $money(abs($t['difference'])) ?> off ITFlow's <?= $statement_money($t['cleared']) ?></div>
|
|
<?php } ?>
|
|
</td>
|
|
<td class="text-nowrap"><a href="transactions.php?account=<?= $account_id ?>&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']) ?>&csrf_token=<?= $_SESSION['csrf_token'] ?>"
|
|
data-confirm-title="Undo this reconciliation?" data-confirm-button="Undo"
|
|
data-confirm-text="<?= $row['opening'] ? 'Nothing before ' . escapeHtml(date('M j, Y', strtotime($row['reconciliation_statement_date'] . ' +1 day'))) . ' counts as reconciled any more' . (abs(floatval($row['reconciliation_adjustment'])) >= 0.005 ? ', and the opening balance goes back' : '') . ' - the next statement can start again from either balance.' : 'It goes back to being reconciled, with what was ticked - its lines can change again until it is finished.' ?>"
|
|
title="<?= $row['opening'] ? 'Take the brought forward away' : '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";
|