Finance bank audit: settlement tab/filter refinements, bank-only selection, and summary metrics
This commit is contained in:
parent
030f674f05
commit
eb13872f43
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
app/Models/FinanceBankSettlementEntryModel.php
Normal file
32
app/Models/FinanceBankSettlementEntryModel.php
Normal 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;
|
||||
}
|
||||
360
app/Views/finance/bank_audit.php
Normal file
360
app/Views/finance/bank_audit.php
Normal 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>
|
||||
59
app/Views/finance/partials/bank_settlement_entries_table.php
Normal file
59
app/Views/finance/partials/bank_settlement_entries_table.php
Normal 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>
|
||||
Loading…
Reference in New Issue
Block a user