devsofttech_hms_ci4/app/Views/finance/bank_audit.php

361 lines
19 KiB
PHP

<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>