Finance bank audit: settlement tab/filter refinements, bank-only selection, and summary metrics

This commit is contained in:
DevSoftTech Server 2026-04-21 19:06:16 +05:30
parent 030f674f05
commit eb13872f43
6 changed files with 1979 additions and 56 deletions

View File

@ -251,44 +251,59 @@ $routes->get('Finance', 'Finance::index');
$routes->get('Finance/index', 'Finance::index');
$routes->get('finance', 'Finance::index');
$routes->get('finance/index', 'Finance::index');
$routes->get('Finance/phase2', 'Finance::phase2');
$routes->get('Finance/section/vendor_master', 'Finance::sectionVendorMaster');
$routes->get('Finance/section/purchase_order', 'Finance::sectionPurchaseOrder');
$routes->get('Finance/section/grn_entry', 'Finance::sectionGrnEntry');
$routes->get('Finance/section/vendor_invoice', 'Finance::sectionVendorInvoice');
$routes->get('Finance/vendors_table', 'Finance::vendorsTable');
$routes->get('Finance/po_table', 'Finance::poTable');
$routes->get('Finance/po_documents/(:num)', 'Finance::poDocuments/$1');
$routes->get('Finance/grn_table', 'Finance::grnTable');
$routes->get('Finance/grn_print/(:num)', 'Finance::grnPrint/$1');
$routes->get('Finance/invoice_table', 'Finance::invoiceTable');
$routes->get('Finance/cashbook', 'Finance::cashbook');
$routes->get('Finance/cashbook', 'Finance::cashbookAccounts');
$routes->get('Finance/cashbook/accounts', 'Finance::cashbookAccounts');
$routes->get('billing/cash-submission', 'Finance::cashbookBilling');
$routes->get('billing/cash-submission/create', 'Finance::cashbookBilling');
$routes->get('Finance/cash_transactions_table', 'Finance::cashTransactionsTable');
$routes->get('Finance/scroll_table', 'Finance::scrollTable');
$routes->get('Finance/doctor_payout', 'Finance::doctorPayout');
$routes->get('Finance/doctor_agreements_table', 'Finance::doctorAgreementsTable');
$routes->get('Finance/doctor_payouts_table', 'Finance::doctorPayoutsTable');
$routes->get('Finance/payment_selection_table', 'Finance::paymentSelectionTable');
$routes->get('Finance/scroll_items_table', 'Finance::scrollItemsTable');
$routes->get('Finance/bank_deposits', 'Finance::bankDeposits');
$routes->get('Finance/bank_deposits_table', 'Finance::bankDepositsTable');
$routes->get('Finance/compliance_report', 'Finance::complianceReport');
$routes->post('Finance/vendor_create', 'Finance::vendorCreate');
$routes->post('Finance/po_create', 'Finance::poCreate');
$routes->post('Finance/po_update', 'Finance::poUpdate');
$routes->post('Finance/grn_create', 'Finance::grnCreate');
$routes->post('Finance/invoice_create', 'Finance::invoiceCreate');
$routes->get('Finance/bank_audit', 'Finance::bankAudit');
$routes->get('Finance/bank_audit_direct_payments_table', 'Finance::bankAuditDirectPaymentsTable');
$routes->get('Finance/bank_statement_entries_table', 'Finance::bankStatementEntriesTable');
$routes->get('Finance/bank_settlement_entries_table', 'Finance::bankSettlementEntriesTable');
$routes->get('Finance/bank_settlement_linked_payments_table', 'Finance::bankSettlementLinkedPaymentsTable');
$routes->get('Finance/bank_settlement_linked_payments_export', 'Finance::bankSettlementLinkedPaymentsExport');
$routes->get('Finance/bank_pos_settlements_table', 'Finance::bankPosSettlementsTable');
$routes->post('Finance/cash_transaction_create', 'Finance::cashTransactionCreate');
$routes->post('Finance/fetch_payments_for_period', 'Finance::fetchPaymentsForPeriod');
$routes->post('Finance/scroll_create', 'Finance::scrollCreate');
$routes->post('Finance/doctor_agreement_create', 'Finance::doctorAgreementCreate');
$routes->post('Finance/doctor_payout_create', 'Finance::doctorPayoutCreate');
$routes->post('Finance/doctor_payout_approve', 'Finance::doctorPayoutApprove');
$routes->post('Finance/scroll_accept', 'Finance::scrollAccept');
$routes->post('Finance/scroll_verify', 'Finance::scrollVerify');
$routes->post('Finance/bank_deposit_create', 'Finance::bankDepositCreate');
$routes->get('Finance/section/pharmacy_bills', 'Finance::sectionPharmacyBills');
$routes->get('Finance/pharmacy_bills_table', 'Finance::pharmacyBillsTable');
$routes->post('Finance/pharmacy_bill_create', 'Finance::pharmacyBillCreate');
$routes->post('Finance/pharmacy_bill_settle', 'Finance::pharmacyBillSettle');
$routes->post('Finance/bank_deposit_status_update', 'Finance::bankDepositStatusUpdate');
$routes->post('Finance/bank_statement_entry_create', 'Finance::bankStatementEntryCreate');
$routes->post('Finance/bank_reconcile_match', 'Finance::bankReconcileMatch');
$routes->post('Finance/bank_reconcile_batch_match', 'Finance::bankReconcileBatchMatch');
$routes->post('Finance/bank_settlement_create', 'Finance::bankSettlementCreate');
$routes->post('Finance/bank_settlement_match_statement', 'Finance::bankSettlementMatchStatement');
$routes->post('Finance/bank_reconcile_unmatch', 'Finance::bankReconcileUnmatch');
$routes->post('Finance/bank_pos_settlement_create', 'Finance::bankPosSettlementCreate');
$routes->post('Finance/bank_pos_settlement_accept', 'Finance::bankPosSettlementAccept');
service('auth')->routes($routes);
// Hospital Stock Management (Storestock)
$routes->get('Storestock', 'Storestock::index');
$routes->get('Storestock/Indent_List', 'Storestock::indent_list');
$routes->match(['get', 'post'], 'Storestock/getIndentTable', 'Storestock::getIndentTable');
$routes->get('Storestock/new_indent', 'Storestock::new_indent');
$routes->post('Storestock/Indent_create/(:num)', 'Storestock::indent_create/$1');
$routes->get('Storestock/Indent_show/(:num)', 'Storestock::indent_show/$1');
$routes->get('Storestock/Indent_Items_list/(:num)', 'Storestock::indent_items_list/$1');
$routes->post('Storestock/add_item/(:num)', 'Storestock::add_item/$1');
$routes->get('Storestock/get_drug', 'Storestock::get_drug');
$routes->get('Storestock/final_invoice/(:num)', 'Storestock::final_invoice/$1');
$routes->get('Storestock/print_single_indent/(:num)', 'Storestock::print_single_indent/$1');
$routes->get('Storestock/store_stock', 'Storestock::store_stock');
$routes->post('Storestock/store_Stock_result', 'Storestock::store_stock_result');
$routes->match(['get', 'post'], 'Storestock/get_product_stock/(:num)', 'Storestock::get_product_stock/$1');
$routes->get('Storestock/Report_2', 'Storestock::report_2');
$routes->get('Storestock/main_store', 'Storestock::main_store');
// Legacy compatibility: some IPD charge screens still refresh via old IpdNew URL.
$routes->get('IpdNew/show_ipd_items/(:num)', 'Billing\\Ipd::panelTab/$1/ipd-charges');
$routes->get('IpdNew/show_ipd_items/(:num)/(:num)', 'Billing\\Ipd::panelTab/$1/ipd-charges');

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,105 @@
<?php
namespace App\Database\Migrations;
use CodeIgniter\Database\Migration;
class AddBankSettlementStatementMatchFields extends Migration
{
public function up(): void
{
if (! $this->hasTable('finance_bank_settlement_entries')) {
return;
}
if (! $this->hasField('finance_bank_settlement_entries', 'reconciliation_status')) {
$this->forge->addColumn('finance_bank_settlement_entries', [
'reconciliation_status' => [
'type' => 'VARCHAR',
'constraint' => 20,
'null' => false,
'default' => 'unmatched',
'after' => 'total_amount',
],
]);
}
if (! $this->hasField('finance_bank_settlement_entries', 'statement_matched_by')) {
$this->forge->addColumn('finance_bank_settlement_entries', [
'statement_matched_by' => [
'type' => 'VARCHAR',
'constraint' => 100,
'null' => true,
'after' => 'reconciliation_status',
],
]);
}
if (! $this->hasField('finance_bank_settlement_entries', 'statement_matched_at')) {
$this->forge->addColumn('finance_bank_settlement_entries', [
'statement_matched_at' => [
'type' => 'DATETIME',
'null' => true,
'after' => 'statement_matched_by',
],
]);
}
if (! $this->hasField('finance_bank_settlement_entries', 'statement_match_remarks')) {
$this->forge->addColumn('finance_bank_settlement_entries', [
'statement_match_remarks' => [
'type' => 'TEXT',
'null' => true,
'after' => 'statement_matched_at',
],
]);
}
$this->db->query("UPDATE finance_bank_settlement_entries SET reconciliation_status = 'unmatched' WHERE reconciliation_status IS NULL OR reconciliation_status = ''");
}
public function down(): void
{
if (! $this->hasTable('finance_bank_settlement_entries')) {
return;
}
if ($this->hasField('finance_bank_settlement_entries', 'statement_match_remarks')) {
$this->forge->dropColumn('finance_bank_settlement_entries', 'statement_match_remarks');
}
if ($this->hasField('finance_bank_settlement_entries', 'statement_matched_at')) {
$this->forge->dropColumn('finance_bank_settlement_entries', 'statement_matched_at');
}
if ($this->hasField('finance_bank_settlement_entries', 'statement_matched_by')) {
$this->forge->dropColumn('finance_bank_settlement_entries', 'statement_matched_by');
}
if ($this->hasField('finance_bank_settlement_entries', 'reconciliation_status')) {
$this->forge->dropColumn('finance_bank_settlement_entries', 'reconciliation_status');
}
}
private function hasTable(string $table): bool
{
try {
$rows = $this->db->query('SHOW TABLES LIKE ' . $this->db->escape($table))->getResultArray();
return ! empty($rows);
} catch (\Throwable $e) {
return false;
}
}
private function hasField(string $table, string $field): bool
{
if (! $this->hasTable($table)) {
return false;
}
try {
$sql = 'SHOW COLUMNS FROM `' . str_replace('`', '``', $table) . '` LIKE ' . $this->db->escape($field);
$rows = $this->db->query($sql)->getResultArray();
return ! empty($rows);
} catch (\Throwable $e) {
return false;
}
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use CodeIgniter\Model;
class FinanceBankSettlementEntryModel extends Model
{
protected $table = 'finance_bank_settlement_entries';
protected $primaryKey = 'id';
protected $returnType = 'array';
protected $useAutoIncrement = true;
protected $allowedFields = [
'settlement_ref',
'settlement_date',
'payment_count',
'total_amount',
'reconciliation_status',
'statement_matched_by',
'statement_matched_at',
'statement_match_remarks',
'remarks',
'created_by',
'created_at',
];
protected $useTimestamps = false;
}

View File

@ -0,0 +1,360 @@
<section class="content finance-bank-audit">
<div class="mb-3">
<h2 class="mb-1">Bank Payment Audit</h2>
<p class="text-muted mb-0">Reconcile bank payments and track settlement matching against your bank statement.</p>
</div>
<div id="bank_audit_alert"></div>
<!-- Summary Cards -->
<div class="row g-2 mb-3">
<div class="col-lg-3 col-md-6 col-6">
<div class="card border-warning">
<div class="card-body py-2">
<div class="small text-muted">System Bank Payments Pending Match</div>
<div class="h5 mb-0 text-warning" id="card_direct_unmatched"><?= (int) ($direct_unmatched ?? 0) ?></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 col-6">
<div class="card border-success">
<div class="card-body py-2">
<div class="small text-muted">System Bank Payments Matched</div>
<div class="h5 mb-0 text-success" id="card_direct_matched"><?= (int) ($direct_matched ?? 0) ?></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 col-6">
<div class="card border-info">
<div class="card-body py-2">
<div class="small text-muted">Settlement Entries Unmatched</div>
<div class="h5 mb-0 text-info" id="card_sett_unmatched"><?= (int) ($sett_unmatched ?? 0) ?></div>
</div>
</div>
</div>
<div class="col-lg-3 col-md-6 col-6">
<div class="card border-primary">
<div class="card-body py-2">
<div class="small text-muted">Settlement Entries Matched</div>
<div class="h5 mb-0 text-primary" id="card_sett_matched"><?= (int) ($sett_matched ?? 0) ?></div>
</div>
</div>
</div>
</div>
<!-- Tabs -->
<ul class="nav nav-tabs mb-3" id="bankAuditTabs">
<li class="nav-item">
<button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tab_direct" type="button">
<i class="bi bi-bank me-1"></i>System Bank Payments
</button>
</li>
<li class="nav-item">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#tab_settlement" type="button">
<i class="bi bi-collection me-1"></i>Settlement Entries
</button>
</li>
</ul>
<div class="tab-content">
<!-- ══ TAB 1: DIRECT TRANSACTIONS ══════════════════════════════════════ -->
<div class="tab-pane fade show active" id="tab_direct">
<div class="row g-3">
<!-- System Bank Payments (shown first on open) -->
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>System Bank Payments</strong>
<div class="d-flex gap-2 flex-nowrap align-items-center overflow-auto">
<input type="date" id="direct_from" class="form-control form-control-sm" style="width:130px" value="<?= date('Y-m-01') ?>">
<input type="date" id="direct_to" class="form-control form-control-sm" style="width:130px" value="<?= date('Y-m-d') ?>">
<select id="direct_status_filter" class="form-select form-select-sm" style="width:120px">
<option value="unmatched">Unmatched</option>
<option value="matched">Matched</option>
<option value="all">All</option>
</select>
<select id="direct_bank_name_filter" class="form-select form-select-sm" style="width:150px">
<?php foreach (($bank_options ?? []) as $b): ?>
<option value="<?= (int) ($b['id'] ?? 0) ?>"><?= esc((string) ($b['bank_name'] ?? '')) ?></option>
<?php endforeach; ?>
</select>
<select id="direct_accepted_by_filter" class="form-select form-select-sm" style="width:220px">
<option value="">All Users</option>
<?php foreach (($accepted_by_users ?? []) as $u): ?>
<option value="<?= (int) ($u['user_id'] ?? 0) ?>"><?= esc((string) ($u['user_name'] ?? '')) ?></option>
<?php endforeach; ?>
</select>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="refreshDirectTable()">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div class="card-body p-0">
<div id="direct_payments_table_wrap"></div>
</div>
</div>
<div class="card border-0 bg-light mt-2">
<div class="card-body py-2 px-2">
<div class="d-flex flex-wrap gap-2 align-items-center">
<input type="text" class="form-control form-control-sm" id="batch_match_remarks" style="min-width:220px;max-width:360px;" placeholder="Match remarks (optional)">
<button type="button" class="btn btn-primary btn-sm" onclick="batchMatchSelectedPayments()">
<i class="bi bi-check2-square me-1"></i>Match Selected
</button>
<input type="text" class="form-control form-control-sm" id="settlement_remarks" style="min-width:220px;max-width:360px;" placeholder="Settlement remarks (optional)">
<button type="button" class="btn btn-warning btn-sm" onclick="createSettlementForSelectedPayments()">
<i class="bi bi-stack me-1"></i>Settlement Selected
</button>
<span class="text-muted small">Use checkboxes in first column for batch confirmation.</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ══ TAB 2: SETTLEMENT ENTRIES ═══════════════════════════════════════ -->
<div class="tab-pane fade" id="tab_settlement">
<div class="row g-3">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>Settlement Entries</strong>
<div class="d-flex gap-2 flex-nowrap align-items-center overflow-auto">
<input type="date" id="sett_from" class="form-control form-control-sm" style="width:130px" value="<?= date('Y-m-01') ?>">
<input type="date" id="sett_to" class="form-control form-control-sm" style="width:130px" value="<?= date('Y-m-d') ?>">
<select id="sett_status_filter" class="form-select form-select-sm" style="width:120px">
<option value="unmatched">Unmatched</option>
<option value="matched">Matched</option>
<option value="all">All</option>
</select>
<select id="sett_bank_filter" class="form-select form-select-sm" style="width:150px">
<?php foreach (($bank_options ?? []) as $b): ?>
<option value="<?= (int) ($b['id'] ?? 0) ?>"><?= esc((string) ($b['bank_name'] ?? '')) ?></option>
<?php endforeach; ?>
</select>
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="refreshSettlementTable()">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div class="card-body p-0">
<div id="settlement_entries_table_wrap"></div>
</div>
</div>
</div>
</div>
</div>
</div><!-- /tab-content -->
</section>
<div class="modal fade" id="settlementPaymentsModal" tabindex="-1" aria-labelledby="settlementPaymentsModalLabel">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="settlementPaymentsModalLabel">Linked Settlement Payments</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0" id="settlement_payments_modal_body">
<div class="p-3 text-muted">Loading...</div>
</div>
</div>
</div>
</div>
<script>
(function () {
var BASE = '<?= base_url() ?>';
function showAlert(msg, ok) {
var box = document.getElementById('bank_audit_alert');
if (!box) return;
box.innerHTML = '<div class="alert ' + (ok ? 'alert-success' : 'alert-danger') + ' alert-dismissible fade show" role="alert">'
+ msg + '<button type="button" class="btn-close" data-bs-dismiss="alert"></button></div>';
}
// ─── Direct / Settlement tables ─────────────────────────────────────────────
window.refreshDirectTable = function () {
var from = document.getElementById('direct_from')?.value || '';
var to = document.getElementById('direct_to')?.value || '';
var status = document.getElementById('direct_status_filter')?.value || 'unmatched';
var bankId = document.getElementById('direct_bank_name_filter')?.value || '';
var acceptedBy = document.getElementById('direct_accepted_by_filter')?.value || '';
load_form_div(BASE + 'Finance/bank_audit_direct_payments_table?from_date=' + encodeURIComponent(from) + '&to_date=' + encodeURIComponent(to) + '&status=' + encodeURIComponent(status) + '&bank_id=' + encodeURIComponent(bankId) + '&accepted_by=' + encodeURIComponent(acceptedBy), 'direct_payments_table_wrap');
};
window.refreshSettlementTable = function () {
var from = document.getElementById('sett_from')?.value || '';
var to = document.getElementById('sett_to')?.value || '';
var status = document.getElementById('sett_status_filter')?.value || 'unmatched';
var bankId = document.getElementById('sett_bank_filter')?.value || '';
load_form_div(BASE + 'Finance/bank_settlement_entries_table?from_date=' + encodeURIComponent(from) + '&to_date=' + encodeURIComponent(to) + '&status=' + encodeURIComponent(status) + '&bank_id=' + encodeURIComponent(bankId), 'settlement_entries_table_wrap');
};
window.matchSettlementWithBankStatement = function (settlementId) {
if (!settlementId) return;
var remarks = window.prompt('Bank statement match remarks (optional):', '') || '';
var fd = new window.FormData();
fd.append('settlement_id', String(settlementId));
fd.append('remarks', remarks);
fetch(BASE + 'Finance/bank_settlement_match_statement', {
method: 'POST',
headers: {'X-Requested-With': 'XMLHttpRequest'},
body: fd
}).then(function (r) { return r.json().then(function (d) { return {ok: r.ok, d: d}; }); })
.then(function (result) {
showAlert(result.d.message || (result.ok ? 'Updated.' : 'Error.'), result.ok && result.d.status === 1);
if (result.ok && result.d.status === 1) {
refreshSettlementTable();
}
}).catch(function () { showAlert('Network error.', false); });
};
window.openSettlementLinkedPayments = function (settlementId, settlementRef) {
if (!settlementId) return;
var title = document.getElementById('settlementPaymentsModalLabel');
if (title) {
title.textContent = 'Linked Payments - ' + (settlementRef || ('Settlement #' + settlementId));
}
var body = document.getElementById('settlement_payments_modal_body');
if (body) {
body.innerHTML = '<div class="p-3 text-muted">Loading...</div>';
}
fetch(BASE + 'Finance/bank_settlement_linked_payments_table?settlement_id=' + encodeURIComponent(String(settlementId)), {
method: 'GET',
headers: {'X-Requested-With': 'XMLHttpRequest'}
}).then(function (r) { return r.text().then(function (html) { return {ok: r.ok, html: html}; }); })
.then(function (result) {
if (!body) return;
if (!result.ok) {
body.innerHTML = '<div class="p-3 text-danger">Unable to load linked payments.</div>';
return;
}
body.innerHTML = result.html;
}).catch(function () {
if (body) {
body.innerHTML = '<div class="p-3 text-danger">Network error while loading linked payments.</div>';
}
});
var modalEl = document.getElementById('settlementPaymentsModal');
if (modalEl && window.bootstrap && window.bootstrap.Modal) {
var modal = window.bootstrap.Modal.getOrCreateInstance(modalEl);
modal.show();
}
};
// ─── Direct match actions ─────────────────────────────────────────────────
window.toggleSelectAllBankPayments = function (checkbox) {
var checks = document.querySelectorAll('.bank-payment-check');
checks.forEach(function (item) { item.checked = !!checkbox.checked; });
};
window.matchSinglePayment = function (paymentId) {
if (!paymentId) return;
var remarks = window.prompt('Bank statement remarks (optional):', '') || '';
var fd = new window.FormData();
fd.append('payment_id', String(paymentId));
fd.append('remarks', remarks);
fetch(BASE + 'Finance/bank_reconcile_match', {
method: 'POST',
headers: {'X-Requested-With': 'XMLHttpRequest'},
body: fd
}).then(function (r) { return r.json().then(function (d) { return {ok: r.ok, d: d}; }); })
.then(function (result) {
showAlert(result.d.message || (result.ok ? 'Matched.' : 'Error.'), result.ok && result.d.status === 1);
if (result.ok && result.d.status === 1) {
refreshDirectTable();
}
}).catch(function () { showAlert('Network error.', false); });
};
window.batchMatchSelectedPayments = function () {
var checks = document.querySelectorAll('.bank-payment-check:checked');
if (!checks.length) {
showAlert('Select at least one payment from first-column checkboxes.', false);
return;
}
var fd = new window.FormData();
checks.forEach(function (cb) { fd.append('payment_ids[]', cb.value); });
fd.append('remarks', document.getElementById('batch_match_remarks')?.value || '');
fetch(BASE + 'Finance/bank_reconcile_batch_match', {
method: 'POST',
headers: {'X-Requested-With': 'XMLHttpRequest'},
body: fd
}).then(function (r) { return r.json().then(function (d) { return {ok: r.ok, d: d}; }); })
.then(function (result) {
showAlert(result.d.message || (result.ok ? 'Batch matched.' : 'Error.'), result.ok && result.d.status === 1);
if (result.ok && result.d.status === 1) {
var remarksBox = document.getElementById('batch_match_remarks');
if (remarksBox) { remarksBox.value = ''; }
refreshDirectTable();
}
}).catch(function () { showAlert('Network error.', false); });
};
window.createSettlementForSelectedPayments = function () {
var checks = document.querySelectorAll('.bank-payment-check:checked');
if (!checks.length) {
showAlert('Select at least one payment from first-column checkboxes.', false);
return;
}
var fd = new window.FormData();
checks.forEach(function (cb) { fd.append('payment_ids[]', cb.value); });
fd.append('remarks', document.getElementById('settlement_remarks')?.value || '');
fetch(BASE + 'Finance/bank_settlement_create', {
method: 'POST',
headers: {'X-Requested-With': 'XMLHttpRequest'},
body: fd
}).then(function (r) { return r.json().then(function (d) { return {ok: r.ok, d: d}; }); })
.then(function (result) {
showAlert(result.d.message || (result.ok ? 'Settlement entry created.' : 'Error.'), result.ok && result.d.status === 1);
if (result.ok && result.d.status === 1) {
var remarksBox = document.getElementById('settlement_remarks');
if (remarksBox) { remarksBox.value = ''; }
refreshDirectTable();
refreshSettlementTable();
}
}).catch(function () { showAlert('Network error.', false); });
};
window.unmatchPayment = function (paymentId) {
if (!confirm('Reverse this match?')) return;
var fd = new window.FormData();
fd.append('payment_id', paymentId);
fetch(BASE + 'Finance/bank_reconcile_unmatch', {
method: 'POST',
headers: {'X-Requested-With': 'XMLHttpRequest'},
body: fd
}).then(function (r) { return r.json().then(function (d) { return {ok: r.ok, d: d}; }); })
.then(function (result) {
showAlert(result.d.message || (result.ok ? 'Done.' : 'Error.'), result.ok && result.d.status === 1);
if (result.ok && result.d.status === 1) { refreshDirectTable(); refreshSettlementTable(); }
}).catch(function () { showAlert('Network error.', false); });
};
// ─── Auto-load on page ready ─────────────────────────────────────────────────
refreshDirectTable();
refreshSettlementTable();
// Re-load tables when switching tabs
document.querySelectorAll('#bankAuditTabs [data-bs-toggle="tab"]').forEach(function (btn) {
btn.addEventListener('shown.bs.tab', function (e) {
if (e.target.dataset.bsTarget === '#tab_direct') { refreshDirectTable(); }
if (e.target.dataset.bsTarget === '#tab_settlement') { refreshSettlementTable(); }
});
});
})();
</script>

View File

@ -0,0 +1,59 @@
<div class="table-responsive">
<table class="table table-sm table-striped align-middle mb-0">
<thead class="table-light">
<tr>
<th>#</th>
<th>Settlement Date</th>
<th>Settlement Ref</th>
<th>Bank Name</th>
<th class="text-end">Payment Count</th>
<th class="text-end">Total Amount</th>
<th>Remarks</th>
<th>Status</th>
<th>Created By</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php if (empty($entries ?? [])): ?>
<tr>
<td colspan="10" class="text-center text-muted py-3">No settlement entries found.</td>
</tr>
<?php else: ?>
<?php $sr = 1; foreach (($entries ?? []) as $row): ?>
<?php $st = trim((string) ($row['reconciliation_status'] ?? 'unmatched')); ?>
<?php $bankNames = trim((string) ($row['bank_names'] ?? '')); ?>
<tr>
<td><?= $sr++ ?></td>
<td class="text-nowrap"><?= esc((string) ($row['settlement_date'] ?? '')) ?></td>
<td class="font-monospace"><?= esc((string) ($row['settlement_ref'] ?? '')) ?></td>
<td><?= esc($bankNames !== '' ? $bankNames : '—') ?></td>
<td class="text-end"><?= (int) ($row['payment_count'] ?? 0) ?></td>
<td class="text-end fw-semibold">₹<?= number_format((float) ($row['total_amount'] ?? 0), 2) ?></td>
<td><?= esc((string) ($row['remarks'] ?? '—')) ?></td>
<td>
<?php if ($st === 'matched'): ?>
<span class="badge bg-success">Matched</span>
<?php else: ?>
<span class="badge bg-warning text-dark">Unmatched</span>
<?php endif; ?>
</td>
<td><?= esc((string) ($row['created_by'] ?? '—')) ?></td>
<td>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="openSettlementLinkedPayments(<?= (int) ($row['id'] ?? 0) ?>, '<?= esc((string) ($row['settlement_ref'] ?? ''), 'js') ?>')">
<i class="bi bi-list-check me-1"></i>View Linked Payments
</button>
<?php if ($st !== 'matched'): ?>
<button type="button" class="btn btn-outline-success btn-sm mt-1"
onclick="matchSettlementWithBankStatement(<?= (int) ($row['id'] ?? 0) ?>)">
<i class="bi bi-check2-circle me-1"></i>Match with Bank Statement
</button>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>