From 1dc378c2dd2ded47ab36de2382aca3d0abf575df Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Fri, 31 Jul 2026 13:39:13 +0530 Subject: [PATCH 1/2] feat(cashier): add Cash/Clearing/Transfer summary report with branch-wise drill-down Adds a dummy-data summary report (Opening/Credit/Debit/Closing) under Sub Ledger > Cash / Clearing / Transfer, with Cash, Transfer (GL-wise), Clearing and Digital (RTGS/NEFT/UPI/ATM/NACH) tabs plus a transaction-details drill down. Credit/Debit/Closing are always derived from the transaction lines so the summary and detail views can never drift apart. Head Office logins (officeId 1 or office name containing "head office") get a branch switcher across two demo branches plus a consolidated view with a branch-wise break-up; branch-level logins see only their own office's data. Co-Authored-By: Claude Sonnet 5 --- ...h-clearing-transfer-summary.component.html | 267 +++ ...h-clearing-transfer-summary.component.scss | 73 + ...ash-clearing-transfer-summary.component.ts | 1535 +++++++++++++++++ src/app/cashier/cashier-routing.module.ts | 11 + .../authentication/authentication.service.ts | 12 + src/app/core/shell/shell.component.html | 1 + .../core/shell/toolbar/toolbar.component.html | 2 +- 7 files changed, 1900 insertions(+), 1 deletion(-) create mode 100644 src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.html create mode 100644 src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.scss create mode 100644 src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.ts diff --git a/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.html b/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.html new file mode 100644 index 000000000..4786e7d46 --- /dev/null +++ b/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.html @@ -0,0 +1,267 @@ + + +
+
+
+
SUB LEDGER · TRANSACTIONS · {{ scopeName | uppercase }}
+

Cash / Clearing / Transfer - Summary Report

+

+ Opening, credit, debit and closing position across Cash, Transfer, Clearing and Digital channels as on + {{ asOnDate }}. +

+
+
+ @if (isHeadOffice) { + + } + +
+
+ +
+
+ Opening + ₹{{ grandOpening | number: '1.2-2' }} +
+
+ Credit + ₹{{ grandCredit | number: '1.2-2' }} +
+
+ Debit + ₹{{ grandDebit | number: '1.2-2' }} +
+
+ Closing + ₹{{ grandClosing | number: '1.2-2' }} +
+
+ + @if (isHeadOffice && selectedBranchCode === 'ALL') { +
+ + + + + + + + + + + + + @for (b of branchBreakup; track b.code) { + + + + + + + + } + + + + + + + + + + +
BranchOpeningCreditDebitClosing
{{ b.name }}₹{{ b.opening | number: '1.2-2' }}₹{{ b.credit | number: '1.2-2' }}₹{{ b.debit | number: '1.2-2' }}₹{{ b.closing | number: '1.2-2' }}
All Branches₹{{ grandOpening | number: '1.2-2' }}₹{{ grandCredit | number: '1.2-2' }}₹{{ grandDebit | number: '1.2-2' }}₹{{ grandClosing | number: '1.2-2' }}
+
+ } + +
+ + + + +
+ + + @if (!showDetails) { +
+ @if (activeTab === 'digital') { + + } + + + + @if (activeTab !== 'digital') { + + } + + + + + + + + + @for (r of rowsForActiveTab; track r.code) { + + @if (activeTab !== 'digital') { + + } + + + + + + + } + + + + + + + + + + +
GL CodeParticularsOpeningCreditDebitClosing
+ {{ r.code }} + {{ r.name }}₹{{ r.opening | number: '1.2-2' }}₹{{ rowCredit(r, txnsForActiveTab) | number: '1.2-2' }}₹{{ rowDebit(r, txnsForActiveTab) | number: '1.2-2' }}₹{{ closing(r, txnsForActiveTab) | number: '1.2-2' }}
Total₹{{ total(rowsForActiveTab, txnsForActiveTab, 'opening') | number: '1.2-2' }}₹{{ total(rowsForActiveTab, txnsForActiveTab, 'credit') | number: '1.2-2' }}₹{{ total(rowsForActiveTab, txnsForActiveTab, 'debit') | number: '1.2-2' }}₹{{ totalClosing(rowsForActiveTab, txnsForActiveTab) | number: '1.2-2' }}
+
+ } + + + @if (showDetails) { + @if (rowsForActiveTab.length > 1) { +
+ + @for (r of rowsForActiveTab; track r.code) { + + } +
+ } + +
+ + + + + + + + + + + + + + @for (t of filteredTxns; track t.voucherNo) { + + + + + + + + + } @empty { + + + + } + + + + + + + + +
DateVoucher / Ref NoAccount / ReferenceParticularsDebitCredit
{{ asOnDate }} + {{ t.voucherNo }} + +
{{ t.accountName }}
+
{{ t.accountNo }}
+
{{ t.particulars }} + @if (t.debit > 0) { + ₹{{ t.debit | number: '1.2-2' }} + } + + @if (t.credit > 0) { + ₹{{ t.credit | number: '1.2-2' }} + } +
No transactions for this filter.
Total₹{{ txnTotal(filteredTxns, 'debit') | number: '1.2-2' }}₹{{ txnTotal(filteredTxns, 'credit') | number: '1.2-2' }}
+
+ } +
diff --git a/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.scss b/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.scss new file mode 100644 index 000000000..e6aa08e9f --- /dev/null +++ b/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.scss @@ -0,0 +1,73 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +.cctr-tabs { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.cctr-total-row { + td { + background: #f1f5f9; + font-weight: 800; + border-top: 2px solid #cbd5e1; + border-bottom: none; + color: #0f172a; + } +} + +.cctr-grand-summary { + display: flex; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 12px; + overflow: hidden; + + &__item { + flex: 1; + padding: 14px 20px; + display: flex; + flex-direction: column; + gap: 4px; + border-right: 1px solid #e2e8f0; + + &:last-child { + border-right: none; + } + } + + &__label { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.07em; + color: #64748b; + } + + &__value { + font-size: 20px; + font-weight: 800; + font-variant-numeric: tabular-nums; + color: #0f172a; + } +} + +.cctr-subtext { + font-size: 11px; + color: #94a3b8; + margin-top: 2px; +} + +.cctr-branch-row { + cursor: pointer; + + &:hover td { + background: #eff6ff; + } +} diff --git a/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.ts b/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.ts new file mode 100644 index 000000000..9e636da20 --- /dev/null +++ b/src/app/cashier/cash-clearing-transfer-summary/cash-clearing-transfer-summary.component.ts @@ -0,0 +1,1535 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { Component, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { AuthenticationService } from '../../core/authentication/authentication.service'; + +interface SummaryRow { + code: string; + name: string; + /** Balance carried forward as of the start of the day — the only figure NOT derived from txns below. */ + opening: number; +} + +interface TxnRow { + /** Links this transaction to the SummaryRow.code it posts against — the single source of truth for Credit/Debit totals. */ + glCode: string; + voucherNo: string; + accountNo: string; + accountName: string; + particulars: string; + debit: number; + credit: number; +} + +interface Branch { + code: string; + name: string; + /** Relative size vs the base dataset below (which represents the Head Office main branch, scale 1). */ + scale: number; +} + +type TabKey = 'cash' | 'transfer' | 'clearing' | 'digital'; + +/** + * Dummy static Cash / Clearing / Transfer summary + transaction-detail report. + * + * IMPORTANT: Credit/Debit (and therefore Closing) are always DERIVED from the txn + * lists below (rowCredit/rowDebit), never hand-entered separately — this guarantees + * the Summary view and the Transaction Details view can never go out of sync, + * for any branch or for the Head Office's "All Branches" consolidated view. + */ +@Component({ + selector: 'mifosx-cash-clearing-transfer-summary', + templateUrl: './cash-clearing-transfer-summary.component.html', + styleUrls: [ + '../cashier-shared.scss', + './cash-clearing-transfer-summary.component.scss' + ], + standalone: true, + imports: [ + CommonModule, + FormsModule + ] +}) +export class CashClearingTransferSummaryComponent { + private auth = inject(AuthenticationService); + + asOnDate = ''; + activeTab: TabKey = 'cash'; + showDetails = false; + /** GL/channel filter for the Transaction Details view — null means "All". */ + glFilter: string | null = null; + + /** True when the logged-in user belongs to the Head Office (sees both branches + consolidated view). */ + isHeadOffice = false; + /** The logged-in user's own branch/office name, from real session credentials. */ + currentOfficeName = 'Branch Office'; + /** Set when a branch-level login's officeName matches one of the two dummy branches below. */ + private ownBranch: Branch | null = null; + + /** + * The two branches this demo covers. A branch-level user is matched to one of these by + * their real officeName (case-insensitive substring match) so "Branch 1" logging in sees + * Branch 1's figures and "Branch 2" sees Branch 2's — rename these to your real office + * names if they differ. Both are proportionally scaled from the same base dataset so + * every branch's own numbers stay internally consistent (Summary always equals the sum + * of its own Transaction Details). + */ + branches: Branch[] = [ + { code: 'BR1', name: 'Branch 1', scale: 1 }, + { code: 'BR2', name: 'Branch 2', scale: 0.7 } + ]; + + /** 'ALL' = consolidated across both branches; otherwise a Branch.code. Only meaningful for Head Office users. */ + selectedBranchCode = 'ALL'; + + cashRows: SummaryRow[] = [{ code: '100001', name: 'Cash at Vault', opening: 3572000 }]; + + transferRows: SummaryRow[] = [ + { code: '250101', name: 'Suspense Account - Transfer', opening: 85000 }, + { code: '250102', name: 'Sundry Deposit Account', opening: 132500 }, + { code: '250103', name: 'Inter Branch Account (Head Office)', opening: 610000 }, + { code: '250104', name: 'Inward Remittance Suspense', opening: 42000 }, + { code: '250105', name: 'Outward Remittance Suspense', opening: 38500 }, + { code: '251001', name: 'Bank GL - SBI Current Account', opening: 1850000 }, + { code: '251002', name: 'Bank GL - HDFC Bank Current Account', opening: 960000 }, + { code: '251003', name: 'Bank GL - ICICI Bank Current Account', opening: 745000 }, + { code: '251004', name: 'Bank GL - Axis Bank Current Account', opening: 512000 }, + { code: '252001', name: 'DD Payable Account', opening: 96000 }, + { code: '252002', name: 'Pay Order Payable Account', opening: 64000 }, + { code: '253001', name: 'Sundry Creditors', opening: 158000 }, + { code: '253002', name: 'Sundry Debtors', opening: 121000 }, + { code: '254001', name: 'TDS Payable Account', opening: 28500 }, + { code: '254002', name: 'GST Payable Account', opening: 19500 } + ]; + + clearingRows: SummaryRow[] = [ + { code: '300201', name: 'Outward Clearing (Cheques Sent)', opening: 265000 }, + { code: '300202', name: 'Inward Clearing (Cheques Received)', opening: 198000 }, + { code: '300203', name: 'Clearing Suspense Account', opening: 15500 }, + { code: '300204', name: 'CTS Clearing Difference Account', opening: 4200 }, + { code: '300205', name: 'Local Clearing House Account', opening: 88000 } + ]; + + digitalRows: SummaryRow[] = [ + { code: 'RTGS', name: 'RTGS - Overall', opening: 720000 }, + { code: 'NEFT', name: 'NEFT - Overall', opening: 512000 }, + { code: 'UPI', name: 'UPI - Overall', opening: 96000 }, + { code: 'ATM', name: 'ATM - Overall', opening: 340000 }, + { code: 'NACH', name: 'NACH - Overall', opening: 158000 } + ]; + + cashTxns: TxnRow[] = [ + { + glCode: '100001', + voucherNo: 'CSH/2607/0141', + accountNo: 'Teller-01', + accountName: 'Teller Counter 1 Cash Deposits (Aggregated)', + particulars: 'Cash Deposit - Savings/Current Accounts', + debit: 0, + credit: 950000 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0142', + accountNo: 'Teller-02', + accountName: 'Teller Counter 2 Cash Deposits (Aggregated)', + particulars: 'Cash Deposit - Savings/Current Accounts', + debit: 0, + credit: 700000 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0143', + accountNo: '015020198765', + accountName: 'Anita Traders', + particulars: 'Cash Deposit - Current Account', + debit: 0, + credit: 185000 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0144', + accountNo: '015010112345', + accountName: 'Ramesh Kumar Sahu', + particulars: 'Cash Deposit - Savings Account', + debit: 0, + credit: 25000 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0145', + accountNo: '015020209988', + accountName: 'Kavita Enterprises', + particulars: 'Cash Deposit - Current Account', + debit: 0, + credit: 110000 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0146', + accountNo: 'Branch ATM', + accountName: 'ATM Excess Cash Retraction', + particulars: 'Excess Cash Surrendered by ATM to Vault', + debit: 0, + credit: 400000 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0147', + accountNo: '015010223344', + accountName: 'Suresh Yadav', + particulars: 'Cash Deposit - Savings Account', + debit: 0, + credit: 15000 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0148', + accountNo: '015030112233', + accountName: 'Kiran Bhosale', + particulars: 'Cash Deposit - RD Installment', + debit: 0, + credit: 130000 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0149', + accountNo: 'Branch ATM', + accountName: 'ATM 1 Cash Loading', + particulars: 'Vault to ATM Cash Transfer', + debit: 700000, + credit: 0 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0150', + accountNo: 'Extension Counter ATM', + accountName: 'ATM 2 Cash Loading', + particulars: 'Vault to ATM Cash Transfer', + debit: 500000, + credit: 0 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0151', + accountNo: '015010154321', + accountName: 'Sunita Devi', + particulars: 'Cash Withdrawal - Savings Account', + debit: 12000, + credit: 0 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0152', + accountNo: '015010167890', + accountName: 'Mohan Lal Verma', + particulars: 'Cash Withdrawal - Term Deposit Closure', + debit: 75000, + credit: 0 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0153', + accountNo: 'Currency Chest', + accountName: 'Currency Chest Remittance', + particulars: 'Surplus Cash Remitted to Currency Chest', + debit: 400000, + credit: 0 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0154', + accountNo: '015020278899', + accountName: 'Prakash Jadhav', + particulars: 'Cash Withdrawal - Current Account', + debit: 210000, + credit: 0 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0155', + accountNo: '015010289900', + accountName: 'Snehal Kulkarni', + particulars: 'Cash Withdrawal - Savings Account', + debit: 18000, + credit: 0 + }, + { + glCode: '100001', + voucherNo: 'CSH/2607/0156', + accountNo: 'Vault', + accountName: 'Vault Denomination Exchange Adjustment', + particulars: 'Shortage Adjustment - Denomination Exchange', + debit: 205000, + credit: 0 + } + ]; + + transferTxns: TxnRow[] = [ + // 250101 - Suspense Account - Transfer (credit 420000 / debit 398000) + { + glCode: '250101', + voucherNo: 'TRF/2607/0101', + accountNo: '015020145678', + accountName: 'Vikram Traders', + particulars: 'Suspense Recovered - Vikram Traders', + debit: 0, + credit: 250000 + }, + { + glCode: '250101', + voucherNo: 'TRF/2607/0102', + accountNo: '015020145991', + accountName: 'Rohit Sharma', + particulars: 'Suspense Recovered - Rohit Sharma', + debit: 0, + credit: 170000 + }, + { + glCode: '250101', + voucherNo: 'TRF/2607/0103', + accountNo: '015020156432', + accountName: 'Meena Enterprises', + particulars: 'Suspense Adjustment - Meena Enterprises', + debit: 230000, + credit: 0 + }, + { + glCode: '250101', + voucherNo: 'TRF/2607/0104', + accountNo: '015020156877', + accountName: 'Kavita Singh', + particulars: 'Suspense Adjustment - Kavita Singh', + debit: 168000, + credit: 0 + }, + // 250102 - Sundry Deposit Account (credit 96000 / debit 110000) + { + glCode: '250102', + voucherNo: 'TRF/2607/0105', + accountNo: '015020167531', + accountName: 'Ashok Furnitures', + particulars: 'Sundry Deposit Received - Ashok Furnitures', + debit: 0, + credit: 60000 + }, + { + glCode: '250102', + voucherNo: 'TRF/2607/0106', + accountNo: '015020167998', + accountName: 'Ganesh Traders', + particulars: 'Sundry Deposit Received - Ganesh Traders', + debit: 0, + credit: 36000 + }, + { + glCode: '250102', + voucherNo: 'TRF/2607/0107', + accountNo: '015020178654', + accountName: 'Geeta Boutique', + particulars: 'Sundry Deposit Refunded - Geeta Boutique', + debit: 70000, + credit: 0 + }, + { + glCode: '250102', + voucherNo: 'TRF/2607/0108', + accountNo: '015020178901', + accountName: 'Manisha Enterprises', + particulars: 'Sundry Deposit Refunded - Manisha Enterprises', + debit: 40000, + credit: 0 + }, + // 250103 - Inter Branch Account, Head Office (credit 1250000 / debit 1180000) + { + glCode: '250103', + voucherNo: 'TRF/2607/0109', + accountNo: 'BR-0007', + accountName: 'Andheri Branch (Branch Code 0007)', + particulars: 'Funds Received from Andheri Branch', + debit: 0, + credit: 750000 + }, + { + glCode: '250103', + voucherNo: 'TRF/2607/0110', + accountNo: 'BR-0015', + accountName: 'Dadar Branch (Branch Code 0015)', + particulars: 'Funds Received from Dadar Branch', + debit: 0, + credit: 500000 + }, + { + glCode: '250103', + voucherNo: 'TRF/2607/0111', + accountNo: 'BR-0012', + accountName: 'Powai Branch (Branch Code 0012)', + particulars: 'Fund Transfer to Powai Branch', + debit: 680000, + credit: 0 + }, + { + glCode: '250103', + voucherNo: 'TRF/2607/0112', + accountNo: 'BR-0019', + accountName: 'Thane Branch (Branch Code 0019)', + particulars: 'Fund Transfer to Thane Branch', + debit: 500000, + credit: 0 + }, + // 250104 - Inward Remittance Suspense (credit 315000 / debit 300000) + { + glCode: '250104', + voucherNo: 'TRF/2607/0113', + accountNo: '015020189712', + accountName: 'Suresh Patil', + particulars: 'Inward Remittance Received - Suresh Patil', + debit: 0, + credit: 190000 + }, + { + glCode: '250104', + voucherNo: 'TRF/2607/0114', + accountNo: '015020189834', + accountName: 'Rahul Deshmukh', + particulars: 'Inward Remittance Received - Rahul Deshmukh', + debit: 0, + credit: 125000 + }, + { + glCode: '250104', + voucherNo: 'TRF/2607/0115', + accountNo: '015020190845', + accountName: 'Lata Joshi', + particulars: 'Inward Remittance Credited - Lata Joshi', + debit: 175000, + credit: 0 + }, + { + glCode: '250104', + voucherNo: 'TRF/2607/0116', + accountNo: '015020190956', + accountName: 'Farhan Sheikh', + particulars: 'Inward Remittance Credited - Farhan Sheikh', + debit: 125000, + credit: 0 + }, + // 250105 - Outward Remittance Suspense (credit 275000 / debit 260000) + { + glCode: '250105', + voucherNo: 'TRF/2607/0117', + accountNo: '015020201456', + accountName: 'Anil Kapoor Traders', + particulars: 'Outward Remittance Booked - Anil Kapoor Traders', + debit: 0, + credit: 160000 + }, + { + glCode: '250105', + voucherNo: 'TRF/2607/0118', + accountNo: '015020201567', + accountName: 'Sameer Qureshi', + particulars: 'Outward Remittance Booked - Sameer Qureshi', + debit: 0, + credit: 115000 + }, + { + glCode: '250105', + voucherNo: 'TRF/2607/0119', + accountNo: '015020212789', + accountName: 'Deepa Enterprises', + particulars: 'Outward Remittance Settled - Deepa Enterprises', + debit: 150000, + credit: 0 + }, + { + glCode: '250105', + voucherNo: 'TRF/2607/0120', + accountNo: '015020212890', + accountName: 'Yogesh Pawar', + particulars: 'Outward Remittance Settled - Yogesh Pawar', + debit: 110000, + credit: 0 + }, + // 251001 - Bank GL - SBI Current Account (credit 3250000 / debit 2980000) + { + glCode: '251001', + voucherNo: 'TRF/2607/0121', + accountNo: 'SBIN0001234', + accountName: 'SBI Mumbai Main Branch', + particulars: 'Inward Settlement via SBI Current A/c', + debit: 0, + credit: 1500000 + }, + { + glCode: '251001', + voucherNo: 'TRF/2607/0122', + accountNo: 'SBIN0002211', + accountName: 'SBI Andheri Branch', + particulars: 'Inward Settlement via SBI Current A/c', + debit: 0, + credit: 1000000 + }, + { + glCode: '251001', + voucherNo: 'TRF/2607/0123', + accountNo: 'SBIN0003322', + accountName: 'SBI Thane Branch', + particulars: 'Inward Settlement via SBI Current A/c', + debit: 0, + credit: 750000 + }, + { + glCode: '251001', + voucherNo: 'TRF/2607/0124', + accountNo: 'SBIN0005678', + accountName: 'SBI Pune Branch', + particulars: 'Outward Settlement via SBI Current A/c', + debit: 1400000, + credit: 0 + }, + { + glCode: '251001', + voucherNo: 'TRF/2607/0125', + accountNo: 'SBIN0006689', + accountName: 'SBI Nashik Branch', + particulars: 'Outward Settlement via SBI Current A/c', + debit: 900000, + credit: 0 + }, + { + glCode: '251001', + voucherNo: 'TRF/2607/0126', + accountNo: 'SBIN0007790', + accountName: 'SBI Nagpur Branch', + particulars: 'Outward Settlement via SBI Current A/c', + debit: 680000, + credit: 0 + }, + // 251002 - Bank GL - HDFC Bank Current Account (credit 1840000 / debit 1725000) + { + glCode: '251002', + voucherNo: 'TRF/2607/0127', + accountNo: 'HDFC0002345', + accountName: 'HDFC Bank Andheri Branch', + particulars: 'Inward Settlement via HDFC Current A/c', + debit: 0, + credit: 900000 + }, + { + glCode: '251002', + voucherNo: 'TRF/2607/0128', + accountNo: 'HDFC0003456', + accountName: 'HDFC Bank Bandra Branch', + particulars: 'Inward Settlement via HDFC Current A/c', + debit: 0, + credit: 600000 + }, + { + glCode: '251002', + voucherNo: 'TRF/2607/0129', + accountNo: 'HDFC0004567', + accountName: 'HDFC Bank Malad Branch', + particulars: 'Inward Settlement via HDFC Current A/c', + debit: 0, + credit: 340000 + }, + { + glCode: '251002', + voucherNo: 'TRF/2607/0130', + accountNo: 'HDFC0006789', + accountName: 'HDFC Bank Borivali Branch', + particulars: 'Outward Settlement via HDFC Current A/c', + debit: 800000, + credit: 0 + }, + { + glCode: '251002', + voucherNo: 'TRF/2607/0131', + accountNo: 'HDFC0007890', + accountName: 'HDFC Bank Goregaon Branch', + particulars: 'Outward Settlement via HDFC Current A/c', + debit: 600000, + credit: 0 + }, + { + glCode: '251002', + voucherNo: 'TRF/2607/0132', + accountNo: 'HDFC0008901', + accountName: 'HDFC Bank Kandivali Branch', + particulars: 'Outward Settlement via HDFC Current A/c', + debit: 325000, + credit: 0 + }, + // 251003 - Bank GL - ICICI Bank Current Account (credit 1420000 / debit 1360000) + { + glCode: '251003', + voucherNo: 'TRF/2607/0133', + accountNo: 'ICIC0003456', + accountName: 'ICICI Bank Thane Branch', + particulars: 'Inward Settlement via ICICI Current A/c', + debit: 0, + credit: 820000 + }, + { + glCode: '251003', + voucherNo: 'TRF/2607/0134', + accountNo: 'ICIC0004567', + accountName: 'ICICI Bank Ghatkopar Branch', + particulars: 'Inward Settlement via ICICI Current A/c', + debit: 0, + credit: 600000 + }, + { + glCode: '251003', + voucherNo: 'TRF/2607/0135', + accountNo: 'ICIC0007890', + accountName: 'ICICI Bank Vashi Branch', + particulars: 'Outward Settlement via ICICI Current A/c', + debit: 760000, + credit: 0 + }, + { + glCode: '251003', + voucherNo: 'TRF/2607/0136', + accountNo: 'ICIC0008901', + accountName: 'ICICI Bank Chembur Branch', + particulars: 'Outward Settlement via ICICI Current A/c', + debit: 600000, + credit: 0 + }, + // 251004 - Bank GL - Axis Bank Current Account (credit 980000 / debit 905000) + { + glCode: '251004', + voucherNo: 'TRF/2607/0137', + accountNo: 'UTIB0004567', + accountName: 'Axis Bank Dadar Branch', + particulars: 'Inward Settlement via Axis Current A/c', + debit: 0, + credit: 560000 + }, + { + glCode: '251004', + voucherNo: 'TRF/2607/0138', + accountNo: 'UTIB0005678', + accountName: 'Axis Bank Sion Branch', + particulars: 'Inward Settlement via Axis Current A/c', + debit: 0, + credit: 420000 + }, + { + glCode: '251004', + voucherNo: 'TRF/2607/0139', + accountNo: 'UTIB0008901', + accountName: 'Axis Bank Kurla Branch', + particulars: 'Outward Settlement via Axis Current A/c', + debit: 505000, + credit: 0 + }, + { + glCode: '251004', + voucherNo: 'TRF/2607/0140', + accountNo: 'UTIB0009012', + accountName: 'Axis Bank Ghansoli Branch', + particulars: 'Outward Settlement via Axis Current A/c', + debit: 400000, + credit: 0 + }, + // 252001 - DD Payable Account (credit 240000 / debit 218000) + { + glCode: '252001', + voucherNo: 'TRF/2607/0141', + accountNo: 'DD/2607/5541', + accountName: 'Sanjay Mehta', + particulars: 'Demand Draft Issued - Sanjay Mehta', + debit: 0, + credit: 140000 + }, + { + glCode: '252001', + voucherNo: 'TRF/2607/0142', + accountNo: 'DD/2607/5556', + accountName: 'Farida Sheikh', + particulars: 'Demand Draft Issued - Farida Sheikh', + debit: 0, + credit: 100000 + }, + { + glCode: '252001', + voucherNo: 'TRF/2607/0143', + accountNo: 'DD/2607/5498', + accountName: 'Rekha Nair', + particulars: 'Demand Draft Paid - Rekha Nair', + debit: 128000, + credit: 0 + }, + { + glCode: '252001', + voucherNo: 'TRF/2607/0144', + accountNo: 'DD/2607/5487', + accountName: 'Girish Rao', + particulars: 'Demand Draft Paid - Girish Rao', + debit: 90000, + credit: 0 + }, + // 252002 - Pay Order Payable Account (credit 175000 / debit 160000) + { + glCode: '252002', + voucherNo: 'TRF/2607/0145', + accountNo: 'PO/2607/1187', + accountName: 'Arvind Traders', + particulars: 'Pay Order Issued - Arvind Traders', + debit: 0, + credit: 100000 + }, + { + glCode: '252002', + voucherNo: 'TRF/2607/0146', + accountNo: 'PO/2607/1192', + accountName: 'Neelam Gupta', + particulars: 'Pay Order Issued - Neelam Gupta', + debit: 0, + credit: 75000 + }, + { + glCode: '252002', + voucherNo: 'TRF/2607/0147', + accountNo: 'PO/2607/1145', + accountName: 'Vandana Enterprises', + particulars: 'Pay Order Paid - Vandana Enterprises', + debit: 95000, + credit: 0 + }, + { + glCode: '252002', + voucherNo: 'TRF/2607/0148', + accountNo: 'PO/2607/1138', + accountName: 'Tushar Bhatt', + particulars: 'Pay Order Paid - Tushar Bhatt', + debit: 65000, + credit: 0 + }, + // 253001 - Sundry Creditors (credit 92000 / debit 85000) + { + glCode: '253001', + voucherNo: 'TRF/2607/0149', + accountNo: '015020223412', + accountName: 'Om Stationery Suppliers', + particulars: 'Vendor Bill Provided For - Om Stationery Suppliers', + debit: 0, + credit: 52000 + }, + { + glCode: '253001', + voucherNo: 'TRF/2607/0150', + accountNo: '015020223567', + accountName: 'Shubham Printers', + particulars: 'Vendor Bill Provided For - Shubham Printers', + debit: 0, + credit: 40000 + }, + { + glCode: '253001', + voucherNo: 'TRF/2607/0151', + accountNo: '015020234598', + accountName: 'Shree Balaji AMC Services', + particulars: 'Vendor Payment - AMC Charges', + debit: 50000, + credit: 0 + }, + { + glCode: '253001', + voucherNo: 'TRF/2607/0152', + accountNo: '015020234678', + accountName: 'Metro Security Services', + particulars: 'Vendor Payment - Security Services', + debit: 35000, + credit: 0 + }, + // 253002 - Sundry Debtors (credit 68000 / debit 74000) + { + glCode: '253002', + voucherNo: 'TRF/2607/0153', + accountNo: '015020245671', + accountName: 'Kiran Hardware', + particulars: 'Recovery from Sundry Debtor - Kiran Hardware', + debit: 0, + credit: 40000 + }, + { + glCode: '253002', + voucherNo: 'TRF/2607/0154', + accountNo: '015020245789', + accountName: 'Vishal Electronics', + particulars: 'Recovery from Sundry Debtor - Vishal Electronics', + debit: 0, + credit: 28000 + }, + { + glCode: '253002', + voucherNo: 'TRF/2607/0155', + accountNo: '015020256784', + accountName: 'Nilesh Electricals', + particulars: 'Advance Given to Sundry Debtor - Nilesh Electricals', + debit: 44000, + credit: 0 + }, + { + glCode: '253002', + voucherNo: 'TRF/2607/0156', + accountNo: '015020256890', + accountName: 'Ramdas Plumbing Works', + particulars: 'Advance Given to Sundry Debtor - Ramdas Plumbing Works', + debit: 30000, + credit: 0 + }, + // 254001 - TDS Payable Account (credit 46000 / debit 40000) + { + glCode: '254001', + voucherNo: 'TRF/2607/0157', + accountNo: 'TDS/2607/9911', + accountName: 'TDS on FD Interest Payouts', + particulars: 'TDS Deducted on FD Interest Payouts', + debit: 0, + credit: 28000 + }, + { + glCode: '254001', + voucherNo: 'TRF/2607/0158', + accountNo: 'TDS/2607/9917', + accountName: 'TDS on RD Interest Payouts', + particulars: 'TDS Deducted on RD Interest Payouts', + debit: 0, + credit: 18000 + }, + { + glCode: '254001', + voucherNo: 'TRF/2607/0159', + accountNo: 'TDS/2607/9902', + accountName: 'Income Tax Department', + particulars: 'TDS Remitted to Government - Quarterly Challan', + debit: 24000, + credit: 0 + }, + { + glCode: '254001', + voucherNo: 'TRF/2607/0160', + accountNo: 'TDS/2607/9903', + accountName: 'Income Tax Department', + particulars: 'TDS Remitted to Government - Adjustment Challan', + debit: 16000, + credit: 0 + }, + // 254002 - GST Payable Account (credit 38000 / debit 32000) + { + glCode: '254002', + voucherNo: 'TRF/2607/0161', + accountNo: 'GST/2607/7731', + accountName: 'GST on Service Charges', + particulars: 'GST Collected on Service Charges', + debit: 0, + credit: 22000 + }, + { + glCode: '254002', + voucherNo: 'TRF/2607/0162', + accountNo: 'GST/2607/7736', + accountName: 'GST on Locker Rent', + particulars: 'GST Collected on Locker Rent', + debit: 0, + credit: 16000 + }, + { + glCode: '254002', + voucherNo: 'TRF/2607/0163', + accountNo: 'GST/2607/7702', + accountName: 'GST Department', + particulars: 'GST Remitted to Government - Monthly Return', + debit: 19000, + credit: 0 + }, + { + glCode: '254002', + voucherNo: 'TRF/2607/0164', + accountNo: 'GST/2607/7703', + accountName: 'GST Department', + particulars: 'GST Remitted to Government - Late Fee Adjustment', + debit: 13000, + credit: 0 + } + ]; + + clearingTxns: TxnRow[] = [ + // 300201 - Outward Clearing, Cheques Sent (credit 980000 / debit 910000) + { + glCode: '300201', + voucherNo: 'CLR/2607/0031', + accountNo: 'CHQ-000123', + accountName: 'Rajesh Kumar Singh', + particulars: 'Cheque No 000123 Sent - Rajesh Kumar Singh', + debit: 0, + credit: 550000 + }, + { + glCode: '300201', + voucherNo: 'CLR/2607/0032', + accountNo: 'CHQ-000129', + accountName: 'Manoj Tiwari', + particulars: 'Cheque No 000129 Sent - Manoj Tiwari', + debit: 0, + credit: 430000 + }, + { + glCode: '300201', + voucherNo: 'CLR/2607/0033', + accountNo: 'CHQ-000145', + accountName: 'Nisha Agarwal', + particulars: 'Outward Clearing Realised - Nisha Agarwal', + debit: 520000, + credit: 0 + }, + { + glCode: '300201', + voucherNo: 'CLR/2607/0034', + accountNo: 'CHQ-000151', + accountName: 'Devendra Kale', + particulars: 'Outward Clearing Realised - Devendra Kale', + debit: 390000, + credit: 0 + }, + // 300202 - Inward Clearing, Cheques Received (credit 875000 / debit 820000) + { + glCode: '300202', + voucherNo: 'CLR/2607/0035', + accountNo: 'CHQ-000456', + accountName: 'Sameer Joshi', + particulars: 'Cheque No 000456 Received - Sameer Joshi', + debit: 0, + credit: 500000 + }, + { + glCode: '300202', + voucherNo: 'CLR/2607/0036', + accountNo: 'CHQ-000461', + accountName: 'Ritu Bansal', + particulars: 'Cheque No 000461 Received - Ritu Bansal', + debit: 0, + credit: 375000 + }, + { + glCode: '300202', + voucherNo: 'CLR/2607/0037', + accountNo: 'CHQ-000478', + accountName: 'Pooja Malhotra', + particulars: 'Inward Clearing Passed for Payment - Pooja Malhotra', + debit: 470000, + credit: 0 + }, + { + glCode: '300202', + voucherNo: 'CLR/2607/0038', + accountNo: 'CHQ-000483', + accountName: 'Ajit Naik', + particulars: 'Inward Clearing Passed for Payment - Ajit Naik', + debit: 350000, + credit: 0 + }, + // 300203 - Clearing Suspense Account (credit 60000 / debit 58000) + { + glCode: '300203', + voucherNo: 'CLR/2607/0039', + accountNo: 'CHQ-000512', + accountName: 'ABC Enterprises', + particulars: 'Clearing Difference Booked - ABC Enterprises', + debit: 0, + credit: 35000 + }, + { + glCode: '300203', + voucherNo: 'CLR/2607/0040', + accountNo: 'CHQ-000519', + accountName: 'Global Traders', + particulars: 'Clearing Difference Booked - Global Traders', + debit: 0, + credit: 25000 + }, + { + glCode: '300203', + voucherNo: 'CLR/2607/0041', + accountNo: 'CHQ-000534', + accountName: 'XYZ Traders', + particulars: 'Cheque Return - Insufficient Funds - XYZ Traders', + debit: 33000, + credit: 0 + }, + { + glCode: '300203', + voucherNo: 'CLR/2607/0042', + accountNo: 'CHQ-000541', + accountName: 'Om Textiles', + particulars: 'Cheque Return - Insufficient Funds - Om Textiles', + debit: 25000, + credit: 0 + }, + // 300204 - CTS Clearing Difference Account (credit 12500 / debit 11000) + { + glCode: '300204', + voucherNo: 'CLR/2607/0043', + accountNo: 'CTS-GRID-01A', + accountName: 'CTS Grid Rounding Adjustment - Batch 1 (Cr)', + particulars: 'CTS Grid Rounding Adjustment - Batch 1', + debit: 0, + credit: 7500 + }, + { + glCode: '300204', + voucherNo: 'CLR/2607/0044', + accountNo: 'CTS-GRID-01B', + accountName: 'CTS Grid Rounding Adjustment - Batch 2 (Cr)', + particulars: 'CTS Grid Rounding Adjustment - Batch 2', + debit: 0, + credit: 5000 + }, + { + glCode: '300204', + voucherNo: 'CLR/2607/0045', + accountNo: 'CTS-GRID-02A', + accountName: 'CTS Grid Rounding Adjustment - Batch 1 (Dr)', + particulars: 'CTS Grid Rounding Adjustment - Batch 1', + debit: 6500, + credit: 0 + }, + { + glCode: '300204', + voucherNo: 'CLR/2607/0046', + accountNo: 'CTS-GRID-02B', + accountName: 'CTS Grid Rounding Adjustment - Batch 2 (Dr)', + particulars: 'CTS Grid Rounding Adjustment - Batch 2', + debit: 4500, + credit: 0 + }, + // 300205 - Local Clearing House Account (credit 310000 / debit 292000) + { + glCode: '300205', + voucherNo: 'CLR/2607/0047', + accountNo: 'MUM-CH-S1', + accountName: 'Mumbai Clearing House - Session I', + particulars: 'CTS Grid Settlement - Session I (Cr)', + debit: 0, + credit: 180000 + }, + { + glCode: '300205', + voucherNo: 'CLR/2607/0048', + accountNo: 'MUM-CH-S2', + accountName: 'Mumbai Clearing House - Session II', + particulars: 'CTS Grid Settlement - Session II (Cr)', + debit: 0, + credit: 130000 + }, + { + glCode: '300205', + voucherNo: 'CLR/2607/0049', + accountNo: 'MUM-CH-S1D', + accountName: 'Mumbai Clearing House - Session I', + particulars: 'CTS Grid Settlement - Session I (Dr)', + debit: 165000, + credit: 0 + }, + { + glCode: '300205', + voucherNo: 'CLR/2607/0050', + accountNo: 'MUM-CH-S2D', + accountName: 'Mumbai Clearing House - Session II', + particulars: 'CTS Grid Settlement - Session II (Dr)', + debit: 127000, + credit: 0 + } + ]; + + digitalTxns: TxnRow[] = [ + // RTGS - Overall (credit 4850000 / debit 4620000) + { + glCode: 'RTGS', + voucherNo: 'RTGS/2607/8891', + accountNo: 'UTR-RTGS26073100234', + accountName: 'Deepak Enterprises', + particulars: 'RTGS Inward - Remitting Bank HDFC', + debit: 0, + credit: 2200000 + }, + { + glCode: 'RTGS', + voucherNo: 'RTGS/2607/8892', + accountNo: 'UTR-RTGS26073100256', + accountName: 'Reliance Distributors Pvt Ltd', + particulars: 'RTGS Inward - Remitting Bank ICICI', + debit: 0, + credit: 1650000 + }, + { + glCode: 'RTGS', + voucherNo: 'RTGS/2607/8893', + accountNo: 'UTR-RTGS26073100278', + accountName: 'Wadia Constructions', + particulars: 'RTGS Inward - Remitting Bank Axis', + debit: 0, + credit: 1000000 + }, + { + glCode: 'RTGS', + voucherNo: 'RTGS/2607/8894', + accountNo: 'UTR-RTGS26073100891', + accountName: 'Kunal Manufacturing Co.', + particulars: 'RTGS Outward - Beneficiary Bank HDFC', + debit: 2000000, + credit: 0 + }, + { + glCode: 'RTGS', + voucherNo: 'RTGS/2607/8895', + accountNo: 'UTR-RTGS26073100904', + accountName: 'Bansal Steel Traders', + particulars: 'RTGS Outward - Beneficiary Bank SBI', + debit: 1600000, + credit: 0 + }, + { + glCode: 'RTGS', + voucherNo: 'RTGS/2607/8896', + accountNo: 'UTR-RTGS26073100918', + accountName: 'Mehta Exports', + particulars: 'RTGS Outward - Beneficiary Bank ICICI', + debit: 1020000, + credit: 0 + }, + // NEFT - Overall (credit 3260000 / debit 3105000) + { + glCode: 'NEFT', + voucherNo: 'NEFT/2607/6712', + accountNo: 'UTR-NEFT26073100589', + accountName: 'Priya Sharma', + particulars: 'NEFT Inward - Remitting Bank SBI', + debit: 0, + credit: 1500000 + }, + { + glCode: 'NEFT', + voucherNo: 'NEFT/2607/6713', + accountNo: 'UTR-NEFT26073100601', + accountName: 'Kunal Oberoi', + particulars: 'NEFT Inward - Remitting Bank HDFC', + debit: 0, + credit: 1100000 + }, + { + glCode: 'NEFT', + voucherNo: 'NEFT/2607/6714', + accountNo: 'UTR-NEFT26073100617', + accountName: 'Sanya Kapoor', + particulars: 'NEFT Inward - Remitting Bank ICICI', + debit: 0, + credit: 660000 + }, + { + glCode: 'NEFT', + voucherNo: 'NEFT/2607/6715', + accountNo: 'UTR-NEFT26073100742', + accountName: 'Sanjay Wholesale Traders', + particulars: 'NEFT Outward - Beneficiary Bank SBI', + debit: 1400000, + credit: 0 + }, + { + glCode: 'NEFT', + voucherNo: 'NEFT/2607/6716', + accountNo: 'UTR-NEFT26073100758', + accountName: 'Rakesh Chandok', + particulars: 'NEFT Outward - Beneficiary Bank Axis', + debit: 1000000, + credit: 0 + }, + { + glCode: 'NEFT', + voucherNo: 'NEFT/2607/6717', + accountNo: 'UTR-NEFT26073100763', + accountName: 'Meera Iyer', + particulars: 'NEFT Outward - Beneficiary Bank HDFC', + debit: 705000, + credit: 0 + }, + // UPI - Overall (credit 5480000 / debit 5390000) - NPCI settlement batched by time slot + { + glCode: 'UPI', + voucherNo: 'UPI/2607/44521', + accountNo: 'NPCI-UPI-SETL-IN-01', + accountName: 'UPI Inward Settlement - Morning Batch (00:00-12:00)', + particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)', + debit: 0, + credit: 1980000 + }, + { + glCode: 'UPI', + voucherNo: 'UPI/2607/44522', + accountNo: 'NPCI-UPI-SETL-IN-02', + accountName: 'UPI Inward Settlement - Afternoon Batch (12:00-18:00)', + particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)', + debit: 0, + credit: 2100000 + }, + { + glCode: 'UPI', + voucherNo: 'UPI/2607/44523', + accountNo: 'NPCI-UPI-SETL-IN-03', + accountName: 'UPI Inward Settlement - Evening Batch (18:00-24:00)', + particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)', + debit: 0, + credit: 1400000 + }, + { + glCode: 'UPI', + voucherNo: 'UPI/2607/44524', + accountNo: 'NPCI-UPI-SETL-OUT-01', + accountName: 'UPI Outward Settlement - Morning Batch (00:00-12:00)', + particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)', + debit: 1850000, + credit: 0 + }, + { + glCode: 'UPI', + voucherNo: 'UPI/2607/44525', + accountNo: 'NPCI-UPI-SETL-OUT-02', + accountName: 'UPI Outward Settlement - Afternoon Batch (12:00-18:00)', + particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)', + debit: 2050000, + credit: 0 + }, + { + glCode: 'UPI', + voucherNo: 'UPI/2607/44526', + accountNo: 'NPCI-UPI-SETL-OUT-03', + accountName: 'UPI Outward Settlement - Evening Batch (18:00-24:00)', + particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)', + debit: 1490000, + credit: 0 + }, + // ATM - Overall (credit 210000 / debit 685000) + { + glCode: 'ATM', + voucherNo: 'ATM/2607/9081', + accountNo: 'ATM-SETL-CR-01', + accountName: 'ATM Cash Deposits - Own Branch ATM (Aggregated)', + particulars: 'ATM Cash Deposit - Own Branch ATM', + debit: 0, + credit: 150000 + }, + { + glCode: 'ATM', + voucherNo: 'ATM/2607/9082', + accountNo: 'ATM-SETL-CR-02', + accountName: 'ATM Cash Deposits - Extension Counter ATM (Aggregated)', + particulars: 'ATM Cash Deposit - Extension Counter ATM', + debit: 0, + credit: 60000 + }, + { + glCode: 'ATM', + voucherNo: 'ATM/2607/9083', + accountNo: 'ATM-SETL-DR-01', + accountName: 'ATM Cash Withdrawals - Own Branch ATM (Aggregated)', + particulars: 'ATM Cash Withdrawal - Own Branch ATM', + debit: 420000, + credit: 0 + }, + { + glCode: 'ATM', + voucherNo: 'ATM/2607/9084', + accountNo: 'ATM-SETL-DR-02', + accountName: 'ATM Cash Withdrawals - Other Bank ATM, NFS Switch (Aggregated)', + particulars: 'ATM Cash Withdrawal - Other Bank ATM (NFS)', + debit: 265000, + credit: 0 + }, + // NACH - Overall (credit 940000 / debit 865000) + { + glCode: 'NACH', + voucherNo: 'NACH/2607/3304', + accountNo: 'NACH-SETL-CR-01', + accountName: 'NACH Credits - Corporate Payroll Disbursement (Aggregated)', + particulars: 'NACH Credit - Salary Disbursement Batch', + debit: 0, + credit: 650000 + }, + { + glCode: 'NACH', + voucherNo: 'NACH/2607/3305', + accountNo: 'NACH-SETL-CR-02', + accountName: 'NACH Credits - Government Subsidy (DBT) Disbursement (Aggregated)', + particulars: 'NACH Credit - Subsidy Disbursement Batch', + debit: 0, + credit: 290000 + }, + { + glCode: 'NACH', + voucherNo: 'NACH/2607/3306', + accountNo: 'NACH-SETL-DR-01', + accountName: 'NACH Debits - Retail Loan EMI Collection (Aggregated)', + particulars: 'NACH Debit - Loan EMI Collection Batch', + debit: 560000, + credit: 0 + }, + { + glCode: 'NACH', + voucherNo: 'NACH/2607/3307', + accountNo: 'NACH-SETL-DR-02', + accountName: 'NACH Debits - Gold Loan EMI Collection (Aggregated)', + particulars: 'NACH Debit - Gold Loan EMI Collection Batch', + debit: 305000, + credit: 0 + } + ]; + + constructor() { + const d = new Date(); + this.asOnDate = `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`; + + const creds = this.auth.getCredentials(); + this.isHeadOffice = this.auth.isHeadOfficeUser(); + this.currentOfficeName = creds?.officeName || 'Branch Office'; + if (this.isHeadOffice) { + // Head Office starts on the consolidated view and can drill into either branch. + this.selectedBranchCode = 'ALL'; + } else { + // Match the logged-in office's real name to one of the two dummy branches, so each + // branch's login shows its own figures. Falls back to the unscaled base dataset + // (still labelled with the user's real office name) if no branch name matches. + this.ownBranch = + this.branches.find((b) => this.currentOfficeName.toLowerCase().includes(b.name.toLowerCase())) || null; + this.selectedBranchCode = this.ownBranch?.code || 'OWN'; + } + } + + private baseRowsFor(tab: TabKey): SummaryRow[] { + switch (tab) { + case 'cash': + return this.cashRows; + case 'transfer': + return this.transferRows; + case 'clearing': + return this.clearingRows; + case 'digital': + return this.digitalRows; + } + } + + private baseTxnsFor(tab: TabKey): TxnRow[] { + switch (tab) { + case 'cash': + return this.cashTxns; + case 'transfer': + return this.transferTxns; + case 'clearing': + return this.clearingTxns; + case 'digital': + return this.digitalTxns; + } + } + + private roundAmt(v: number): number { + return Math.round(v / 100) * 100; + } + + private scaleRows(rows: SummaryRow[], factor: number): SummaryRow[] { + if (factor === 1) { + return rows; + } + return rows.map((r) => ({ ...r, opening: this.roundAmt(r.opening * factor) })); + } + + private scaleTxns(txns: TxnRow[], factor: number, branchTag?: string): TxnRow[] { + if (factor === 1) { + return txns; + } + return txns.map((t) => ({ + ...t, + voucherNo: branchTag ? `${branchTag}-${t.voucherNo}` : t.voucherNo, + debit: this.roundAmt(t.debit * factor), + credit: this.roundAmt(t.credit * factor) + })); + } + + /** Rows for a given tab, scoped to the currently selected branch (or consolidated across both, for Head Office). */ + private effectiveRows(tab: TabKey): SummaryRow[] { + const base = this.baseRowsFor(tab); + if (!this.isHeadOffice) { + return this.ownBranch ? this.scaleRows(base, this.ownBranch.scale) : base; + } + if (this.selectedBranchCode === 'ALL') { + return base.map((r) => ({ + ...r, + opening: this.branches.reduce((sum, b) => sum + this.roundAmt(r.opening * b.scale), 0) + })); + } + const branch = this.branches.find((b) => b.code === this.selectedBranchCode); + return this.scaleRows(base, branch ? branch.scale : 1); + } + + /** Transactions for a given tab, scoped to the currently selected branch (or merged across both, for Head Office). */ + private effectiveTxns(tab: TabKey): TxnRow[] { + const base = this.baseTxnsFor(tab); + if (!this.isHeadOffice) { + return this.ownBranch ? this.scaleTxns(base, this.ownBranch.scale, this.ownBranch.code) : base; + } + if (this.selectedBranchCode === 'ALL') { + return this.branches.flatMap((b) => this.scaleTxns(base, b.scale, b.code)); + } + const branch = this.branches.find((b) => b.code === this.selectedBranchCode); + return this.scaleTxns(base, branch ? branch.scale : 1, branch?.code); + } + + get rowsForActiveTab(): SummaryRow[] { + return this.effectiveRows(this.activeTab); + } + + get txnsForActiveTab(): TxnRow[] { + return this.effectiveTxns(this.activeTab); + } + + /** Name of the branch/scope currently being viewed, for display in the header. */ + get scopeName(): string { + if (!this.isHeadOffice) { + return this.currentOfficeName; + } + if (this.selectedBranchCode === 'ALL') { + return 'All Branches (Consolidated)'; + } + return this.branches.find((b) => b.code === this.selectedBranchCode)?.name || ''; + } + + setBranch(code: string): void { + this.selectedBranchCode = code; + this.glFilter = null; + } + + setTab(tab: TabKey): void { + this.activeTab = tab; + this.glFilter = null; + } + + toggleDetails(): void { + this.showDetails = !this.showDetails; + this.glFilter = null; + } + + setGlFilter(code: string | null): void { + this.glFilter = code; + } + + /** Transactions for the Transaction Details view, narrowed to the selected GL/channel filter (or all, when null). */ + get filteredTxns(): TxnRow[] { + if (!this.glFilter) { + return this.txnsForActiveTab; + } + return this.txnsForActiveTab.filter((t) => t.glCode === this.glFilter); + } + + /** Short label for a GL filter chip — the channel code for Digital (RTGS/NEFT/...), the GL name otherwise. */ + filterLabel(row: SummaryRow): string { + return this.activeTab === 'digital' ? row.code : row.name; + } + + /** Credit total for a GL row — always summed from its own txn lines, never a separate hand-entered number. */ + rowCredit(row: SummaryRow, txns: TxnRow[]): number { + return txns.filter((t) => t.glCode === row.code).reduce((sum, t) => sum + t.credit, 0); + } + + /** Debit total for a GL row — always summed from its own txn lines, never a separate hand-entered number. */ + rowDebit(row: SummaryRow, txns: TxnRow[]): number { + return txns.filter((t) => t.glCode === row.code).reduce((sum, t) => sum + t.debit, 0); + } + + closing(row: SummaryRow, txns: TxnRow[]): number { + return row.opening + this.rowCredit(row, txns) - this.rowDebit(row, txns); + } + + total(rows: SummaryRow[], txns: TxnRow[], key: 'opening' | 'credit' | 'debit'): number { + if (key === 'opening') { + return rows.reduce((sum, r) => sum + r.opening, 0); + } + if (key === 'credit') { + return rows.reduce((sum, r) => sum + this.rowCredit(r, txns), 0); + } + return rows.reduce((sum, r) => sum + this.rowDebit(r, txns), 0); + } + + totalClosing(rows: SummaryRow[], txns: TxnRow[]): number { + return this.total(rows, txns, 'opening') + this.total(rows, txns, 'credit') - this.total(rows, txns, 'debit'); + } + + txnTotal(rows: TxnRow[], key: 'debit' | 'credit'): number { + return rows.reduce((sum, r) => sum + r[key], 0); + } + + private readonly allTabs: TabKey[] = [ + 'cash', + 'transfer', + 'clearing', + 'digital' + ]; + + /** Cash/Transfer/Clearing/Digital category pairs, scoped to the currently selected branch. */ + private get categories(): Array<{ rows: SummaryRow[]; txns: TxnRow[] }> { + return this.allTabs.map((tab) => ({ rows: this.effectiveRows(tab), txns: this.effectiveTxns(tab) })); + } + + get grandOpening(): number { + return this.categories.reduce((sum, c) => sum + this.total(c.rows, c.txns, 'opening'), 0); + } + + get grandCredit(): number { + return this.categories.reduce((sum, c) => sum + this.total(c.rows, c.txns, 'credit'), 0); + } + + get grandDebit(): number { + return this.categories.reduce((sum, c) => sum + this.total(c.rows, c.txns, 'debit'), 0); + } + + get grandClosing(): number { + return this.grandOpening + this.grandCredit - this.grandDebit; + } + + /** + * Branch-wise contribution to the day's overall position — shown only to Head Office + * users while viewing "All Branches". Each branch's figures are derived the same way + * (opening scaled, credit/debit summed from that branch's own scaled txns), so this + * table can never drift from what you'd see by drilling into that branch individually. + */ + get branchBreakup(): Array<{ + code: string; + name: string; + opening: number; + credit: number; + debit: number; + closing: number; + }> { + return this.branches.map((b) => { + const pairs = this.allTabs.map((tab) => ({ + rows: this.scaleRows(this.baseRowsFor(tab), b.scale), + txns: this.scaleTxns(this.baseTxnsFor(tab), b.scale, b.code) + })); + const opening = pairs.reduce((sum, c) => sum + this.total(c.rows, c.txns, 'opening'), 0); + const credit = pairs.reduce((sum, c) => sum + this.total(c.rows, c.txns, 'credit'), 0); + const debit = pairs.reduce((sum, c) => sum + this.total(c.rows, c.txns, 'debit'), 0); + return { code: b.code, name: b.name, opening, credit, debit, closing: opening + credit - debit }; + }); + } +} diff --git a/src/app/cashier/cashier-routing.module.ts b/src/app/cashier/cashier-routing.module.ts index 86568fc2a..fcfb63214 100644 --- a/src/app/cashier/cashier-routing.module.ts +++ b/src/app/cashier/cashier-routing.module.ts @@ -179,6 +179,17 @@ const routes: Routes = [ loadComponent: () => import('./my-deposit-requests/my-deposit-requests.component').then((m) => m.MyDepositRequestsComponent), data: { title: 'My Deposit Requests', breadcrumb: 'My Deposit Requests' } + }, + { + path: 'cash-clearing-transfer-summary', + loadComponent: () => + import('./cash-clearing-transfer-summary/cash-clearing-transfer-summary.component').then( + (m) => m.CashClearingTransferSummaryComponent + ), + data: { + title: 'Cash / Clearing / Transfer - Summary Report', + breadcrumb: 'Cash / Clearing / Transfer - Summary Report' + } } ] } diff --git a/src/app/core/authentication/authentication.service.ts b/src/app/core/authentication/authentication.service.ts index 9d1ae8204..abab9396b 100644 --- a/src/app/core/authentication/authentication.service.ts +++ b/src/app/core/authentication/authentication.service.ts @@ -424,6 +424,18 @@ export class AuthenticationService { return roleNames.some((n) => n === 'cashier'); } + /** + * Returns true when the logged-in user belongs to the Head Office. + * Fineract seeds Head Office as officeId 1 with hierarchy "."; office name is + * checked too since some deployments rename/reseed the root office. + */ + isHeadOfficeUser(): boolean { + const creds = this.getCredentials(); + if (!creds) return false; + if (creds.officeId === 1) return true; + return (creds.officeName || '').toLowerCase().includes('head office'); + } + /** * Sets the user credentials. * diff --git a/src/app/core/shell/shell.component.html b/src/app/core/shell/shell.component.html index bfb5c140c..053738d9d 100644 --- a/src/app/core/shell/shell.component.html +++ b/src/app/core/shell/shell.component.html @@ -653,6 +653,7 @@ Cash / Clearing / Transfer diff --git a/src/app/core/shell/toolbar/toolbar.component.html b/src/app/core/shell/toolbar/toolbar.component.html index ea21b1b2c..786287c56 100644 --- a/src/app/core/shell/toolbar/toolbar.component.html +++ b/src/app/core/shell/toolbar/toolbar.component.html @@ -266,7 +266,7 @@ Cash / Clearing / Transfer From a560cd65336e253286181d61e6293f88210063e3 Mon Sep 17 00:00:00 2001 From: Anshul Rathore Date: Fri, 31 Jul 2026 14:06:27 +0530 Subject: [PATCH 2/2] feat: add Welcome Kit dispatch dialog and Share Holders view --- .../clients-view/clients-view.component.html | 3 + .../clients-view/clients-view.component.ts | 20 + .../welcome-kit-dialog.component.html | 94 +++++ .../welcome-kit-dialog.component.scss | 59 +++ .../welcome-kit-dialog.component.ts | 347 ++++++++++++++++++ src/app/clients/clients.service.ts | 8 + src/app/core/shell/shell.component.html | 2 +- src/app/home/home-routing.module.ts | 4 + .../share-holders.component.html | 130 +++++++ .../share-holders.component.scss | 127 +++++++ .../share-holders/share-holders.component.ts | 199 ++++++++++ src/app/shares/shares-routing.module.ts | 15 +- src/app/shares/shares.module.ts | 18 + src/app/shares/shares.service.ts | 33 ++ src/environments/.env.ts | 4 +- 15 files changed, 1051 insertions(+), 12 deletions(-) create mode 100644 src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.html create mode 100644 src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.scss create mode 100644 src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.ts create mode 100644 src/app/shares/share-holders/share-holders.component.html create mode 100644 src/app/shares/share-holders/share-holders.component.scss create mode 100644 src/app/shares/share-holders/share-holders.component.ts diff --git a/src/app/clients/clients-view/clients-view.component.html b/src/app/clients/clients-view/clients-view.component.html index a4cd5f25d..2bc58ab72 100644 --- a/src/app/clients/clients-view/clients-view.component.html +++ b/src/app/clients/clients-view/clients-view.component.html @@ -410,6 +410,9 @@ + diff --git a/src/app/clients/clients-view/clients-view.component.ts b/src/app/clients/clients-view/clients-view.component.ts index 0c8bf3bf5..0d914d5d5 100644 --- a/src/app/clients/clients-view/clients-view.component.ts +++ b/src/app/clients/clients-view/clients-view.component.ts @@ -22,6 +22,7 @@ import { DrawSignatureDialogComponent } from './custom-dialogs/draw-signature-di import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; import { UploadImageDialogComponent } from './custom-dialogs/upload-image-dialog/upload-image-dialog.component'; import { CaptureImageDialogComponent } from './custom-dialogs/capture-image-dialog/capture-image-dialog.component'; +import { WelcomeKitDialogComponent } from './custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component'; /** Custom Services */ import { ClientsService } from '../clients.service'; @@ -255,6 +256,9 @@ export class ClientsViewComponent implements OnInit { case 'Delete Image': this.deleteProfileImage(); break; + case 'Welcome Kit': + this.dispatchWelcomeKit(); + break; case 'Create Standing Instructions': const createStandingInstructionsQueryParams: any = { officeId: this.clientViewData.officeId, @@ -455,4 +459,20 @@ export class ClientsViewComponent implements OnInit { } }); } + + /** + * Opens the Welcome Kit Dispatch dialog. + */ + private dispatchWelcomeKit() { + const welcomeKitDialogRef = this.dialog.open(WelcomeKitDialogComponent, { + data: { clientId: this.clientViewData.id }, + width: '600px' + }); + welcomeKitDialogRef.afterClosed().subscribe((response: any) => { + if (response) { + // the form was successfully submitted + this.reload(); + } + }); + } } diff --git a/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.html b/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.html new file mode 100644 index 000000000..d7f979973 --- /dev/null +++ b/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.html @@ -0,0 +1,94 @@ + +

Welcome Kit Dispatch

+ + +
+ +
+ + + +
+
+

{{ i === 0 ? 'Current' : 'Previous' }} Welcome Kit Status

+ +
+

Delivery Address: {{ getDisplayAddress(dispatch) }}

+

Tracking ID: {{ dispatch.trackingId }}

+

+ Delivery Mode: {{ dispatch.deliveryMode }} + ({{ dispatch.deliveryModeOtherText }}) +

+

Dispatched On: {{ dispatch.dispatchedOnDate | date: 'mediumDate' }}

+

+ Status: + {{ dispatch.status }} +

+
+ +
+ + +
+

Create New Dispatch

+
+ + Delivery Address + + + Delivery Address is required + + + + + Delivery Mode + + + {{ mode }} + + + + Delivery Mode is required + + + + + Specify Other Mode + + + Please specify the delivery mode + + +
+
+
+
+ + + + + diff --git a/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.scss b/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.scss new file mode 100644 index 000000000..4eea9cdf5 --- /dev/null +++ b/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.scss @@ -0,0 +1,59 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +.dispatch-tracking-panel { + background-color: #f5f5f5; + border-radius: 8px; + padding: 16px; + margin-bottom: 16px; + + h3 { + margin-top: 0; + margin-bottom: 12px; + font-size: 1.1rem; + } + + p { + margin: 6px 0; + } + + .status-badge { + padding: 4px 8px; + border-radius: 4px; + font-size: 0.85rem; + font-weight: 500; + color: white; + + &.status-dispatched { + background-color: #1976d2; + } + + &.status-in_transit { + background-color: #f57c00; + } + + &.status-delivered { + background-color: #388e3c; + } + + &.status-returned { + background-color: #d32f2f; + } + } +} + +.dispatch-create-form { + h3 { + margin-top: 0; + margin-bottom: 12px; + font-size: 1.1rem; + } +} + +.full-width { + width: 100%; +} diff --git a/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.ts b/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.ts new file mode 100644 index 000000000..b3d72602b --- /dev/null +++ b/src/app/clients/clients-view/custom-dialogs/welcome-kit-dialog/welcome-kit-dialog.component.ts @@ -0,0 +1,347 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { Component, OnInit, inject } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { + MAT_DIALOG_DATA, + MatDialogRef, + MatDialogTitle, + MatDialogContent, + MatDialogActions, + MatDialogClose +} from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { ClientsService } from '../../../clients.service'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { DatePipe } from '@angular/common'; +import { jsPDF } from 'jspdf'; +import autoTable from 'jspdf-autotable'; + +@Component({ + selector: 'mifosx-welcome-kit-dialog', + templateUrl: './welcome-kit-dialog.component.html', + styleUrls: ['./welcome-kit-dialog.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDialogTitle, + MatDialogContent, + MatDialogActions, + MatDialogClose, + MatProgressSpinnerModule + ], + providers: [DatePipe] +}) +export class WelcomeKitDialogComponent implements OnInit { + private dialogRef = inject(MatDialogRef); + data: { clientId: string } = inject(MAT_DIALOG_DATA); + private clientsService = inject(ClientsService); + private formBuilder = inject(FormBuilder); + private snackBar = inject(MatSnackBar); + private datePipe = inject(DatePipe); + + isLoading = true; + welcomeKitsData: any = null; + dispatchForm: FormGroup = this.formBuilder.group({ + deliveryAddress: [ + '', + Validators.required + ], + deliveryMode: [ + 'POSTMAN', + Validators.required + ], + deliveryModeOtherText: [''] + }); + deliveryModes = [ + 'POSTMAN', + 'COURIER', + 'OTHER' + ]; + + constructor() { + this.dispatchForm.get('deliveryMode')?.valueChanges.subscribe((mode) => { + const otherControl = this.dispatchForm.get('deliveryModeOtherText'); + if (mode === 'OTHER') { + otherControl?.setValidators([Validators.required]); + } else { + otherControl?.clearValidators(); + } + otherControl?.updateValueAndValidity(); + }); + } + + ngOnInit(): void { + this.fetchData(); + } + + fetchData() { + this.isLoading = true; + this.clientsService.getWelcomeKits(this.data.clientId).subscribe({ + next: (response: any) => { + this.welcomeKitsData = response; + if (this.welcomeKitsData && this.welcomeKitsData.canDispatchNew) { + this.fetchPrimaryAddress(); + } else { + this.isLoading = false; + } + }, + error: () => { + this.isLoading = false; + this.snackBar.open('Failed to load welcome kits data', 'Close', { duration: 3000 }); + } + }); + } + + fetchPrimaryAddress() { + this.clientsService.getClientAddressData(this.data.clientId).subscribe({ + next: (addresses: any) => { + this.isLoading = false; + if (addresses && addresses.length > 0) { + // Attempt to build a readable address from the first address returned + const primary = addresses[0]; + const parts = []; + if (primary.addressLine1) parts.push(primary.addressLine1); + if (primary.addressLine2) parts.push(primary.addressLine2); + if (primary.city) parts.push(primary.city); + if (primary.stateName) parts.push(primary.stateName); + if (primary.postalCode) parts.push(primary.postalCode); + if (primary.countryName) parts.push(primary.countryName); + + const clientNameStr = this.welcomeKitsData?.clientName ? this.welcomeKitsData.clientName + ' — ' : ''; + this.dispatchForm.patchValue({ + deliveryAddress: clientNameStr + parts.join(', ') + }); + } + }, + error: () => { + this.isLoading = false; + } + }); + } + + get activeDispatches() { + if (this.welcomeKitsData && this.welcomeKitsData.dispatches && this.welcomeKitsData.dispatches.length > 0) { + return this.welcomeKitsData.dispatches; + } + return []; + } + + submit() { + if (this.dispatchForm.invalid) { + this.dispatchForm.markAllAsTouched(); + return; + } + + this.isLoading = true; + const formValue = this.dispatchForm.value; + + const today = new Date(); + const formattedDate = this.datePipe.transform(today, 'dd MMMM yyyy'); + + const payload = { + deliveryAddress: formValue.deliveryAddress, + deliveryMode: formValue.deliveryMode, + deliveryModeOtherText: formValue.deliveryMode === 'OTHER' ? formValue.deliveryModeOtherText : null, + dispatchedOnDate: formattedDate, + locale: 'en', + dateFormat: 'dd MMMM yyyy' + }; + + this.clientsService.dispatchWelcomeKit(this.data.clientId, payload).subscribe({ + next: () => { + this.snackBar.open('Welcome Kit Dispatched successfully', 'Close', { duration: 3000 }); + this.fetchData(); + }, + error: () => { + this.isLoading = false; + this.snackBar.open('Failed to dispatch welcome kit', 'Close', { duration: 3000 }); + } + }); + } + + getDisplayAddress(dispatch: any): string { + const address = dispatch.deliveryAddress || ''; + const name = this.welcomeKitsData?.clientName || ''; + if (name && !address.startsWith(name)) { + return `${name} — ${address}`; + } + return address; + } + + downloadPdf(dispatch: any) { + const doc = new jsPDF(); + const clientName = this.welcomeKitsData?.clientName || dispatch.clientName || 'N/A'; + const address = this.getDisplayAddress(dispatch); + const mode = + dispatch.deliveryMode === 'OTHER' + ? `${dispatch.deliveryMode} (${dispatch.deliveryModeOtherText || ''})` + : dispatch.deliveryMode; + const trackingId = dispatch.trackingId || 'N/A'; + const status = dispatch.status || 'N/A'; + const dispatchedOn = this.datePipe.transform(dispatch.dispatchedOnDate, 'mediumDate') || 'N/A'; + + const primaryColor: [number, number, number] = [ + 31, + 73, + 125 + ]; // Navy Blue + const grayColor: [number, number, number] = [ + 100, + 100, + 100 + ]; + const lightGray: [number, number, number] = [ + 230, + 230, + 230 + ]; + const pageWidth = doc.internal.pageSize.getWidth(); + const pageHeight = doc.internal.pageSize.getHeight(); + + // --- 1. Header Banner --- + doc.setFillColor(...primaryColor); + doc.rect(0, 0, pageWidth, 40, 'F'); + + // Decorative circles + doc.setFillColor(255, 255, 255); + doc.circle(pageWidth - 20, 15, 3, 'F'); + doc.circle(pageWidth - 30, 25, 2, 'F'); + doc.circle(pageWidth - 10, 30, 1.5, 'F'); + + doc.setFontSize(28); + doc.setFont('helvetica', 'bold'); + doc.setTextColor(255, 255, 255); + doc.text('CBSNexora', 20, 22); + + doc.setFontSize(11); + doc.setFont('helvetica', 'normal'); + doc.text('Core Banking Solution', 22, 30); + + // --- 2. Title --- + let currentY = 55; + doc.setFontSize(16); + doc.setFont('helvetica', 'bold'); + doc.setTextColor(...primaryColor); + doc.text('Welcome Kit Dispatch Confirmation', pageWidth / 2, currentY, { align: 'center' }); + currentY += 12; + + // --- 3. Date Line --- + doc.setFontSize(11); + doc.setFont('helvetica', 'normal'); + doc.setTextColor(0, 0, 0); + const today = this.datePipe.transform(new Date(), 'longDate'); + doc.text(`Date: ${today}`, pageWidth - 20, currentY, { align: 'right' }); + currentY += 15; + + // --- 4. Greeting --- + doc.text(`Dear ${clientName},`, 20, currentY); + currentY += 10; + + // --- 5. Opening welcome paragraph --- + const p1 = + 'Welcome to the CBSNexora family! We are delighted to have you on board as a valued client. As part of our onboarding process, we have dispatched your Welcome Kit to your registered address, which includes your account documentation and essential banking materials to help you get started with us.'; + const splitP1 = doc.splitTextToSize(p1, pageWidth - 40); + doc.text(splitP1, 20, currentY); + currentY += splitP1.length * 6 + 8; + + // --- 6. Short paragraph about the company --- + const p2 = + 'CBSNexora is committed to providing secure, reliable, and customer-first banking services. Our team is always available to assist you with any banking needs, from account management to digital services, ensuring a seamless banking experience.'; + const splitP2 = doc.splitTextToSize(p2, pageWidth - 40); + doc.text(splitP2, 20, currentY); + currentY += splitP2.length * 6 + 8; + + // --- 7. Transition line before details --- + doc.text('Please find your Welcome Kit dispatch details below for your reference:', 20, currentY); + currentY += 8; + + // --- 8. Details Section --- + autoTable(doc, { + startY: currentY, + theme: 'plain', + margin: { left: 20, right: 20 }, + styles: { fontSize: 11, cellPadding: 6, textColor: [ + 0, + 0, + 0 + ] }, + columnStyles: { + 0: { fontStyle: 'bold', cellWidth: 50 }, + 1: { cellWidth: 'auto' } + }, + didDrawCell: (data) => { + if (data.row.index >= 0) { + doc.setDrawColor(...lightGray); + doc.setLineWidth(0.2); + doc.line( + data.cell.x, + data.cell.y + data.cell.height, + data.cell.x + data.cell.width, + data.cell.y + data.cell.height + ); + } + }, + body: [ + [ + 'Delivery Address', + address + ], + [ + 'Delivery Mode', + mode + ], + [ + 'Tracking ID', + trackingId + ], + [ + 'Status', + status + ], + [ + 'Dispatched On', + dispatchedOn + ] + ] + }); + + // --- 9. Closing paragraph --- + currentY = (doc as any).lastAutoTable.finalY + 12; + const p3 = + 'Should you have any questions about your Welcome Kit or need any assistance, please do not hesitate to reach out to our support team. We look forward to serving you and building a long-term relationship with you.'; + const splitP3 = doc.splitTextToSize(p3, pageWidth - 40); + doc.text(splitP3, 20, currentY); + currentY += splitP3.length * 6 + 12; + + // --- 10. Sign-off --- + doc.text('Warm regards,', 20, currentY); + currentY += 7; + doc.setFont('helvetica', 'bold'); + doc.text('CBSNexora Operations Team', 20, currentY); + + // --- 11. Footer --- + // Ensure footer doesn't float too high on short pages, but pushes down if content goes long + let footerY = currentY + 30; + if (footerY < pageHeight - 30) { + footerY = pageHeight - 30; + } + + doc.setDrawColor(...primaryColor); + doc.setLineWidth(0.3); + doc.line(20, footerY - 5, pageWidth - 20, footerY - 5); + + doc.setFontSize(9); + doc.setFont('helvetica', 'italic'); + doc.setTextColor(...grayColor); + const timestamp = this.datePipe.transform(new Date(), 'medium'); + doc.text(`This is a system-generated document. Generated on: ${timestamp}`, 20, footerY + 2); + + doc.save(`WelcomeKit_Dispatch_${trackingId !== 'N/A' ? trackingId : new Date().getTime()}.pdf`); + } +} diff --git a/src/app/clients/clients.service.ts b/src/app/clients/clients.service.ts index cbb893260..14a2efc49 100644 --- a/src/app/clients/clients.service.ts +++ b/src/app/clients/clients.service.ts @@ -950,4 +950,12 @@ export class ClientsService { submitClientForm60(clientId: string, data: any): Observable { return this.http.post(`/clients/${clientId}/form60`, data); } + + getWelcomeKits(clientId: string | number): Observable { + return this.http.get(`/clients/${clientId}/welcomekit`); + } + + dispatchWelcomeKit(clientId: string | number, data: any): Observable { + return this.http.post(`/clients/${clientId}/welcomekit`, data); + } } diff --git a/src/app/core/shell/shell.component.html b/src/app/core/shell/shell.component.html index bfb5c140c..4dd81b25f 100644 --- a/src/app/core/shell/shell.component.html +++ b/src/app/core/shell/shell.component.html @@ -1121,7 +1121,7 @@ - + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Client Name{{ element.clientName || 'N/A' }}Account No{{ element.accountNo || 'N/A' }}Share Product Name{{ element.productName || 'N/A' }}Approved Shares{{ element.totalApprovedShares || 0 }}Pending Shares{{ element.totalPendingForApprovalShares || 0 }}Currency{{ element.currency?.code || 'N/A' }}Activated On + {{ element.activatedDate ? (element.activatedDate | dateFormat) : 'Not Activated' }} +
+ + +
+ diff --git a/src/app/shares/share-holders/share-holders.component.scss b/src/app/shares/share-holders/share-holders.component.scss new file mode 100644 index 000000000..6d41b20a0 --- /dev/null +++ b/src/app/shares/share-holders/share-holders.component.scss @@ -0,0 +1,127 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +.container { + margin: 1.5rem; + padding: 0; + border-radius: var(--radius-xl); + background: var(--bg-card); + box-shadow: var(--shadow-sm); +} + +.table-header { + padding: 1.5rem; + border-bottom: 1px solid var(--border-light); + + .table-header-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + flex-wrap: wrap; + gap: 1rem; + } + + .table-title-group { + display: flex; + align-items: center; + gap: 1rem; + + .table-title { + margin: 0; + font-size: 1.25rem; + font-weight: 600; + color: var(--text-main); + display: flex; + align-items: center; + gap: 0.75rem; + letter-spacing: -0.01em; + + .t-logomark { + width: 24px; + height: 24px; + position: relative; + display: flex; + flex-wrap: wrap; + gap: 2px; + padding: 2px; + background: var(--bg-hover); + border-radius: 6px; + + span { + width: 8px; + height: 8px; + border-radius: 2px; + background: var(--primary-main); + opacity: 0.8; + + &:nth-child(2) { + opacity: 0.6; + } + + &:nth-child(3) { + opacity: 0.4; + } + + &:nth-child(4) { + opacity: 0.2; + } + } + } + } + + .table-count { + padding: 0.25rem 0.75rem; + background: var(--bg-hover); + color: var(--text-muted); + border-radius: var(--radius-full); + font-size: 0.75rem; + font-weight: 500; + letter-spacing: 0.02em; + } + } + + .table-filters { + display: flex; + gap: 1rem; + align-items: center; + flex-wrap: wrap; + + mat-form-field { + width: 240px; + margin-bottom: -1.25em; + } + } +} + +.table-container { + position: relative; + overflow: auto; + border-radius: 0 0 var(--radius-xl) var(--radius-xl); + + .table-loader { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 10; + } +} + +.modern-table { + width: 100%; + background: transparent; + + tr.mat-row { + cursor: pointer; + transition: background-color 0.2s ease; + + &:hover { + background-color: var(--bg-hover); + } + } +} diff --git a/src/app/shares/share-holders/share-holders.component.ts b/src/app/shares/share-holders/share-holders.component.ts new file mode 100644 index 000000000..18d302ff5 --- /dev/null +++ b/src/app/shares/share-holders/share-holders.component.ts @@ -0,0 +1,199 @@ +/** + * Copyright since 2025 Mifos Initiative + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { Component, OnInit, ViewChild, inject } from '@angular/core'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatPaginator, MatPaginatorModule } from '@angular/material/paginator'; +import { MatSort, MatSortModule } from '@angular/material/sort'; +import { MatCardModule } from '@angular/material/card'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; +import { MatDatepickerModule } from '@angular/material/datepicker'; +import { MatNativeDateModule } from '@angular/material/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { FormsModule, ReactiveFormsModule, FormGroup, FormControl } from '@angular/forms'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { forkJoin, of } from 'rxjs'; +import { catchError, debounceTime, distinctUntilChanged, filter, switchMap, tap, startWith } from 'rxjs/operators'; +import { DateFormatPipe } from '../../pipes/date-format.pipe'; +import { SharesService } from '../shares.service'; + +@Component({ + selector: 'mifosx-share-holders', + templateUrl: './share-holders.component.html', + styleUrls: ['./share-holders.component.scss'], + standalone: true, + imports: [ + CommonModule, + RouterModule, + MatCardModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatProgressBarModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + MatDatepickerModule, + MatNativeDateModule, + MatIconModule, + MatButtonModule, + MatTooltipModule, + FormsModule, + ReactiveFormsModule, + DateFormatPipe + ] +}) +export class ShareHoldersComponent implements OnInit { + displayedColumns: string[] = [ + 'clientName', + 'accountNo', + 'productName', + 'totalApprovedShares', + 'totalPendingForApprovalShares', + 'currency', + 'activatedDate' + ]; + dataSource = new MatTableDataSource(); + isLoading = true; + totalRows = 0; + + // Filter Data + offices: any[] = []; + products: any[] = []; + + // Filter State + searchValue = ''; + branchFilter = ''; + productFilter = ''; + statusFilter = ''; + dateRangeForm = new FormGroup({ + start: new FormControl(null), + end: new FormControl(null) + }); + + @ViewChild(MatPaginator, { static: true }) paginator!: MatPaginator; + @ViewChild(MatSort, { static: true }) sort!: MatSort; + + searchControl = new FormControl(''); + + private sharesService = inject(SharesService); + + ngOnInit(): void { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + this.setupFilterPredicate(); + + this.searchControl.valueChanges + .pipe( + startWith(''), + debounceTime(400), + distinctUntilChanged(), + filter((term: string | null) => !term || term.trim().length === 0 || term.trim().length >= 2), + tap((term: string | null) => { + this.searchValue = term || ''; + this.isLoading = true; + }), + switchMap((term: string | null) => { + const search = term ? term.trim() : ''; + return forkJoin({ + accounts: this.sharesService.getShareAccountSummaries(search || undefined).pipe(catchError(() => of([]))), + offices: this.sharesService.getOffices().pipe(catchError(() => of([]))), + products: this.sharesService.getShareProducts().pipe(catchError(() => of([]))) + }); + }) + ) + .subscribe({ + next: (results: any) => { + this.offices = results.offices || []; + + const allShareAccounts = results.accounts || []; + + // Derive unique products from the actual active share accounts to ensure no garbage/dummy + // products (like NextGen Tech Pvt Ltd) show up in the dropdown. + const uniqueProductsMap = new Map(); + allShareAccounts.forEach((acc: any) => { + if (acc.productId && acc.productName) { + uniqueProductsMap.set(acc.productId, { id: acc.productId, name: acc.productName }); + } + }); + this.products = Array.from(uniqueProductsMap.values()); + + this.dataSource.data = allShareAccounts; + this.totalRows = allShareAccounts.length; + this.isLoading = false; + }, + error: () => { + this.isLoading = false; + } + }); + } + + setupFilterPredicate() { + this.dataSource.filterPredicate = (data: any, filter: string) => { + let branchMatch = true; + if (this.branchFilter) { + branchMatch = data.officeId?.toString() === this.branchFilter.toString(); + } + + let productMatch = true; + if (this.productFilter) { + productMatch = data.productId?.toString() === this.productFilter.toString(); + } + + let statusMatch = true; + if (this.statusFilter) { + statusMatch = + data.status?.value?.toLowerCase() === this.statusFilter.toLowerCase() || + data.status?.id?.toString() === this.statusFilter.toString(); + } + + let dateMatch = true; + if (this.dateRangeForm.value.start && data.activatedDate) { + const rowDate = new Date(data.activatedDate[0], data.activatedDate[1] - 1, data.activatedDate[2]); + const start = this.dateRangeForm.value.start; + const end = this.dateRangeForm.value.end || new Date(); // If no end date, use today + + // Reset hours for accurate comparison + rowDate.setHours(0, 0, 0, 0); + start.setHours(0, 0, 0, 0); + end.setHours(0, 0, 0, 0); + + dateMatch = rowDate >= start && rowDate <= end; + } + + return branchMatch && productMatch && statusMatch && dateMatch; + }; + } + + applyFilters() { + // We update the filter string to a random/unique value to trigger the filterPredicate + this.dataSource.filter = Math.random().toString(); + } + + search(filterValue: string) { + this.searchControl.setValue(filterValue); + } + + clearFilters() { + this.searchValue = ''; + this.branchFilter = ''; + this.productFilter = ''; + this.statusFilter = ''; + this.dateRangeForm.reset(); + + // Trigger server-side reload without search term + this.searchControl.setValue('', { emitEvent: true }); + // Trigger client-side filters update + this.applyFilters(); + } +} diff --git a/src/app/shares/shares-routing.module.ts b/src/app/shares/shares-routing.module.ts index b062886da..4c5193aef 100644 --- a/src/app/shares/shares-routing.module.ts +++ b/src/app/shares/shares-routing.module.ts @@ -18,7 +18,7 @@ import { DividendsTabComponent } from './shares-account-view/dividends-tab/divid import { CreateSharesAccountComponent } from './create-shares-account/create-shares-account.component'; import { EditSharesAccountComponent } from './edit-shares-account/edit-shares-account.component'; import { SharesAccountActionsComponent } from './shares-account-actions/shares-account-actions.component'; -import { EditShareAccountDetailsComponent } from './edit-share-account-details/edit-share-account-details.component'; +import { ShareHoldersComponent } from './share-holders/share-holders.component'; /** Custom Resolvers */ import { SharesAccountViewResolver } from './common-resolvers/share-account-view.resolver'; @@ -33,6 +33,11 @@ const routes: Routes = [ path: '', data: { title: 'Shares', breadcrumb: 'Shares', routeParamBreadcrumb: false, addBreadcrumbLink: false }, children: [ + { + path: 'share-holders', + data: { title: 'Share Holders', breadcrumb: 'Share Holders' }, + component: ShareHoldersComponent + }, { path: 'create', data: { title: 'Create Shares Account', breadcrumb: 'Create Shares Account' }, @@ -90,14 +95,6 @@ const routes: Routes = [ sharesAccountAndTemplate: SharesAccountAndTemplateResolver } }, - { - path: 'edit-details', - data: { title: 'Edit Share Account Details', breadcrumb: 'Edit Details', routeParamBreadcrumb: false }, - component: EditShareAccountDetailsComponent, - resolve: { - sharesAccountAndTemplate: SharesAccountAndTemplateResolver - } - }, { path: 'actions/:name', data: { title: 'Shares Account Actions', breadcrumb: 'Actions', routeParamBreadcrumb: 'name' }, diff --git a/src/app/shares/shares.module.ts b/src/app/shares/shares.module.ts index cf13126e5..6a88f04fc 100644 --- a/src/app/shares/shares.module.ts +++ b/src/app/shares/shares.module.ts @@ -37,6 +37,15 @@ import { ApproveShareDialogComponent } from './shares-account-actions/approve-sh import { RejectSharesComponent } from './shares-account-actions/reject-shares/reject-shares.component'; import { RejectShareDialogComponent } from './shares-account-actions/reject-shares/reject-share-dialog/reject-share-dialog.component'; import { GeneralTabComponent } from './shares-account-view/general-tab/general-tab.component'; +import { ShareHoldersComponent } from './share-holders/share-holders.component'; + +import { MatTableModule } from '@angular/material/table'; +import { MatPaginatorModule } from '@angular/material/paginator'; +import { MatSortModule } from '@angular/material/sort'; +import { MatCardModule } from '@angular/material/card'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; /** * Shares Module @@ -45,6 +54,15 @@ import { GeneralTabComponent } from './shares-account-view/general-tab/general-t */ @NgModule({ imports: [ + ShareHoldersComponent, + SharedModule, + MatTableModule, + MatPaginatorModule, + MatSortModule, + MatCardModule, + MatProgressBarModule, + MatFormFieldModule, + MatInputModule, SharedModule, PipesModule, DirectivesModule, diff --git a/src/app/shares/shares.service.ts b/src/app/shares/shares.service.ts index 1dc19b090..956093a20 100644 --- a/src/app/shares/shares.service.ts +++ b/src/app/shares/shares.service.ts @@ -22,6 +22,39 @@ import { Observable } from 'rxjs'; export class SharesService { private http = inject(HttpClient); + /** + * @param {any} params Optional HTTP params (like offset, limit, search) + * @returns {Observable} List of all share accounts + */ + getSharesAccounts(params?: any): Observable { + return this.http.get('/accounts/share', { params }); + } + + /** + * @returns {Observable} List of all share accounts with client details summary + */ + getShareAccountSummaries(searchTerm?: string): Observable { + let params = new HttpParams(); + if (searchTerm) { + params = params.set('searchTerm', searchTerm); + } + return this.http.get('/shareaccounts/summary', { params }); + } + + /** + * @returns {Observable} List of all offices + */ + getOffices(): Observable { + return this.http.get('/offices'); + } + + /** + * @returns {Observable} List of all share products + */ + getShareProducts(): Observable { + return this.http.get('/products/share'); + } + /** * @param {string} accountId Shares Account Id of account to get data for. * @param {boolean} template Shares account template required?. diff --git a/src/environments/.env.ts b/src/environments/.env.ts index f7521f4b2..f94dce0fc 100644 --- a/src/environments/.env.ts +++ b/src/environments/.env.ts @@ -9,8 +9,8 @@ /* tslint:disable */ export default { 'mifos_x': { - 'version': '260729', - 'hash': 'c1fc95d9' + 'version': '260731', + 'hash': 'ca85bae4' }, 'allow_switching_backend_instance': true };