fix(cashier): realistic branch-aware account numbers, dynamic date tags in summary report

Account No now always holds a genuine reference for its transaction type
(customer account, GL/facility code, IFSC, DD/PO/TDS/GST/cheque ref, or
UTR/RRN/UMRN for RTGS/NEFT/UPI/ATM/NACH) instead of a plain description.
Branch 2's view derives its own valid-format account numbers and IFSC
codes by shifting the numeric segment, rather than tagging a "BR2-" prefix
onto Branch 1's values, which looked fake for standardized formats.

Voucher/reference numbers' baked-in "2607" (Jul 2026) tag is now derived
from the real current date at render time, so it won't go stale next month.

feat(clients): auto-calculate Monthly/Annual Income and Turnover

Typing either the monthly or annual figure now fills in the other at a 12x
ratio (rounded to 2 decimals, no trailing ".00" on whole numbers). Guarded
against firing during prefillForEdit()'s initial load of an existing
client's independently-saved values, so editing doesn't silently overwrite
historical data that wasn't an exact 12x match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 17:24:57 +05:30
co-authored by Claude Sonnet 5
parent b9b37397fe
commit fd28788d23
3 changed files with 119 additions and 40 deletions
@@ -219,7 +219,8 @@
<tr> <tr>
<th>Date</th> <th>Date</th>
<th>Voucher / Ref No</th> <th>Voucher / Ref No</th>
<th>Account / Reference</th> <th>Account No</th>
<th>Account Name / Reference</th>
<th>Particulars</th> <th>Particulars</th>
<th class="num col-out">Debit</th> <th class="num col-out">Debit</th>
<th class="num col-in">Credit</th> <th class="num col-in">Credit</th>
@@ -230,12 +231,10 @@
<tr> <tr>
<td>{{ asOnDate }}</td> <td>{{ asOnDate }}</td>
<td> <td>
<span class="pg-chip">{{ t.voucherNo }}</span> <span class="pg-chip">{{ display(t.voucherNo) }}</span>
</td>
<td>
<div>{{ t.accountName }}</div>
<div class="cctr-subtext">{{ t.accountNo }}</div>
</td> </td>
<td>{{ display(t.accountNo) }}</td>
<td>{{ t.accountName }}</td>
<td>{{ t.particulars }}</td> <td>{{ t.particulars }}</td>
<td class="num col-out"> <td class="num col-out">
@if (t.debit > 0) { @if (t.debit > 0) {
@@ -250,13 +249,13 @@
</tr> </tr>
} @empty { } @empty {
<tr> <tr>
<td colspan="6" class="pg-empty">No transactions for this filter.</td> <td colspan="7" class="pg-empty">No transactions for this filter.</td>
</tr> </tr>
} }
</tbody> </tbody>
<tfoot> <tfoot>
<tr class="cctr-total-row"> <tr class="cctr-total-row">
<td colspan="4">Total</td> <td colspan="5">Total</td>
<td class="num">₹{{ txnTotal(filteredTxns, 'debit') | number: '1.2-2' }}</td> <td class="num">₹{{ txnTotal(filteredTxns, 'debit') | number: '1.2-2' }}</td>
<td class="num">₹{{ txnTotal(filteredTxns, 'credit') | number: '1.2-2' }}</td> <td class="num">₹{{ txnTotal(filteredTxns, 'credit') | number: '1.2-2' }}</td>
</tr> </tr>
@@ -34,6 +34,9 @@ interface Branch {
name: string; name: string;
/** Relative size vs the base dataset below (which represents the Head Office main branch, scale 1). */ /** Relative size vs the base dataset below (which represents the Head Office main branch, scale 1). */
scale: number; scale: number;
/** Numeric offset used to derive this branch's own account numbers / IFSC serials — keeps the
* standard 12-digit / IFSC format intact instead of just tagging a prefix onto Branch 1's values. */
acctShift: number;
} }
type TabKey = 'cash' | 'transfer' | 'clearing' | 'digital'; type TabKey = 'cash' | 'transfer' | 'clearing' | 'digital';
@@ -84,8 +87,8 @@ export class CashClearingTransferSummaryComponent {
* of its own Transaction Details). * of its own Transaction Details).
*/ */
branches: Branch[] = [ branches: Branch[] = [
{ code: 'BR1', name: 'Branch 1', scale: 1 }, { code: 'BR1', name: 'Branch 1', scale: 1, acctShift: 0 },
{ code: 'BR2', name: 'Branch 2', scale: 0.7 } { code: 'BR2', name: 'Branch 2', scale: 0.7, acctShift: 400 }
]; ];
/** 'ALL' = consolidated across both branches; otherwise a Branch.code. Only meaningful for Head Office users. */ /** 'ALL' = consolidated across both branches; otherwise a Branch.code. Only meaningful for Head Office users. */
@@ -131,7 +134,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: '100001', glCode: '100001',
voucherNo: 'CSH/2607/0141', voucherNo: 'CSH/2607/0141',
accountNo: 'Teller-01', accountNo: 'TIL-0001',
accountName: 'Teller Counter 1 Cash Deposits (Aggregated)', accountName: 'Teller Counter 1 Cash Deposits (Aggregated)',
particulars: 'Cash Deposit - Savings/Current Accounts', particulars: 'Cash Deposit - Savings/Current Accounts',
debit: 0, debit: 0,
@@ -140,7 +143,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: '100001', glCode: '100001',
voucherNo: 'CSH/2607/0142', voucherNo: 'CSH/2607/0142',
accountNo: 'Teller-02', accountNo: 'TIL-0002',
accountName: 'Teller Counter 2 Cash Deposits (Aggregated)', accountName: 'Teller Counter 2 Cash Deposits (Aggregated)',
particulars: 'Cash Deposit - Savings/Current Accounts', particulars: 'Cash Deposit - Savings/Current Accounts',
debit: 0, debit: 0,
@@ -176,7 +179,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: '100001', glCode: '100001',
voucherNo: 'CSH/2607/0146', voucherNo: 'CSH/2607/0146',
accountNo: 'Branch ATM', accountNo: 'ATM-0001',
accountName: 'ATM Excess Cash Retraction', accountName: 'ATM Excess Cash Retraction',
particulars: 'Excess Cash Surrendered by ATM to Vault', particulars: 'Excess Cash Surrendered by ATM to Vault',
debit: 0, debit: 0,
@@ -203,7 +206,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: '100001', glCode: '100001',
voucherNo: 'CSH/2607/0149', voucherNo: 'CSH/2607/0149',
accountNo: 'Branch ATM', accountNo: 'ATM-0001',
accountName: 'ATM 1 Cash Loading', accountName: 'ATM 1 Cash Loading',
particulars: 'Vault to ATM Cash Transfer', particulars: 'Vault to ATM Cash Transfer',
debit: 700000, debit: 700000,
@@ -212,7 +215,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: '100001', glCode: '100001',
voucherNo: 'CSH/2607/0150', voucherNo: 'CSH/2607/0150',
accountNo: 'Extension Counter ATM', accountNo: 'ATM-0002',
accountName: 'ATM 2 Cash Loading', accountName: 'ATM 2 Cash Loading',
particulars: 'Vault to ATM Cash Transfer', particulars: 'Vault to ATM Cash Transfer',
debit: 500000, debit: 500000,
@@ -239,7 +242,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: '100001', glCode: '100001',
voucherNo: 'CSH/2607/0153', voucherNo: 'CSH/2607/0153',
accountNo: 'Currency Chest', accountNo: 'CCH-0001',
accountName: 'Currency Chest Remittance', accountName: 'Currency Chest Remittance',
particulars: 'Surplus Cash Remitted to Currency Chest', particulars: 'Surplus Cash Remitted to Currency Chest',
debit: 400000, debit: 400000,
@@ -266,7 +269,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: '100001', glCode: '100001',
voucherNo: 'CSH/2607/0156', voucherNo: 'CSH/2607/0156',
accountNo: 'Vault', accountNo: 'VLT-0001',
accountName: 'Vault Denomination Exchange Adjustment', accountName: 'Vault Denomination Exchange Adjustment',
particulars: 'Shortage Adjustment - Denomination Exchange', particulars: 'Shortage Adjustment - Denomination Exchange',
debit: 205000, debit: 205000,
@@ -1171,7 +1174,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'UPI', glCode: 'UPI',
voucherNo: 'UPI/2607/44521', voucherNo: 'UPI/2607/44521',
accountNo: 'NPCI-UPI-SETL-IN-01', accountNo: 'RRN-UPI26073100145',
accountName: 'UPI Inward Settlement - Morning Batch (00:00-12:00)', accountName: 'UPI Inward Settlement - Morning Batch (00:00-12:00)',
particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)', particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)',
debit: 0, debit: 0,
@@ -1180,7 +1183,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'UPI', glCode: 'UPI',
voucherNo: 'UPI/2607/44522', voucherNo: 'UPI/2607/44522',
accountNo: 'NPCI-UPI-SETL-IN-02', accountNo: 'RRN-UPI26073100289',
accountName: 'UPI Inward Settlement - Afternoon Batch (12:00-18:00)', accountName: 'UPI Inward Settlement - Afternoon Batch (12:00-18:00)',
particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)', particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)',
debit: 0, debit: 0,
@@ -1189,7 +1192,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'UPI', glCode: 'UPI',
voucherNo: 'UPI/2607/44523', voucherNo: 'UPI/2607/44523',
accountNo: 'NPCI-UPI-SETL-IN-03', accountNo: 'RRN-UPI26073100356',
accountName: 'UPI Inward Settlement - Evening Batch (18:00-24:00)', accountName: 'UPI Inward Settlement - Evening Batch (18:00-24:00)',
particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)', particulars: 'UPI Collect - Merchant/P2P Inward (Aggregated)',
debit: 0, debit: 0,
@@ -1198,7 +1201,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'UPI', glCode: 'UPI',
voucherNo: 'UPI/2607/44524', voucherNo: 'UPI/2607/44524',
accountNo: 'NPCI-UPI-SETL-OUT-01', accountNo: 'RRN-UPI26073100478',
accountName: 'UPI Outward Settlement - Morning Batch (00:00-12:00)', accountName: 'UPI Outward Settlement - Morning Batch (00:00-12:00)',
particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)', particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)',
debit: 1850000, debit: 1850000,
@@ -1207,7 +1210,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'UPI', glCode: 'UPI',
voucherNo: 'UPI/2607/44525', voucherNo: 'UPI/2607/44525',
accountNo: 'NPCI-UPI-SETL-OUT-02', accountNo: 'RRN-UPI26073100512',
accountName: 'UPI Outward Settlement - Afternoon Batch (12:00-18:00)', accountName: 'UPI Outward Settlement - Afternoon Batch (12:00-18:00)',
particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)', particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)',
debit: 2050000, debit: 2050000,
@@ -1216,7 +1219,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'UPI', glCode: 'UPI',
voucherNo: 'UPI/2607/44526', voucherNo: 'UPI/2607/44526',
accountNo: 'NPCI-UPI-SETL-OUT-03', accountNo: 'RRN-UPI26073100634',
accountName: 'UPI Outward Settlement - Evening Batch (18:00-24:00)', accountName: 'UPI Outward Settlement - Evening Batch (18:00-24:00)',
particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)', particulars: 'UPI Pay - Merchant/P2P Outward (Aggregated)',
debit: 1490000, debit: 1490000,
@@ -1226,7 +1229,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'ATM', glCode: 'ATM',
voucherNo: 'ATM/2607/9081', voucherNo: 'ATM/2607/9081',
accountNo: 'ATM-SETL-CR-01', accountNo: 'RRN-ATM26073100118',
accountName: 'ATM Cash Deposits - Own Branch ATM (Aggregated)', accountName: 'ATM Cash Deposits - Own Branch ATM (Aggregated)',
particulars: 'ATM Cash Deposit - Own Branch ATM', particulars: 'ATM Cash Deposit - Own Branch ATM',
debit: 0, debit: 0,
@@ -1235,7 +1238,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'ATM', glCode: 'ATM',
voucherNo: 'ATM/2607/9082', voucherNo: 'ATM/2607/9082',
accountNo: 'ATM-SETL-CR-02', accountNo: 'RRN-ATM26073100224',
accountName: 'ATM Cash Deposits - Extension Counter ATM (Aggregated)', accountName: 'ATM Cash Deposits - Extension Counter ATM (Aggregated)',
particulars: 'ATM Cash Deposit - Extension Counter ATM', particulars: 'ATM Cash Deposit - Extension Counter ATM',
debit: 0, debit: 0,
@@ -1244,7 +1247,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'ATM', glCode: 'ATM',
voucherNo: 'ATM/2607/9083', voucherNo: 'ATM/2607/9083',
accountNo: 'ATM-SETL-DR-01', accountNo: 'RRN-ATM26073100337',
accountName: 'ATM Cash Withdrawals - Own Branch ATM (Aggregated)', accountName: 'ATM Cash Withdrawals - Own Branch ATM (Aggregated)',
particulars: 'ATM Cash Withdrawal - Own Branch ATM', particulars: 'ATM Cash Withdrawal - Own Branch ATM',
debit: 420000, debit: 420000,
@@ -1253,7 +1256,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'ATM', glCode: 'ATM',
voucherNo: 'ATM/2607/9084', voucherNo: 'ATM/2607/9084',
accountNo: 'ATM-SETL-DR-02', accountNo: 'RRN-ATM26073100449',
accountName: 'ATM Cash Withdrawals - Other Bank ATM, NFS Switch (Aggregated)', accountName: 'ATM Cash Withdrawals - Other Bank ATM, NFS Switch (Aggregated)',
particulars: 'ATM Cash Withdrawal - Other Bank ATM (NFS)', particulars: 'ATM Cash Withdrawal - Other Bank ATM (NFS)',
debit: 265000, debit: 265000,
@@ -1263,7 +1266,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'NACH', glCode: 'NACH',
voucherNo: 'NACH/2607/3304', voucherNo: 'NACH/2607/3304',
accountNo: 'NACH-SETL-CR-01', accountNo: 'UMRN-NACH26073100112',
accountName: 'NACH Credits - Corporate Payroll Disbursement (Aggregated)', accountName: 'NACH Credits - Corporate Payroll Disbursement (Aggregated)',
particulars: 'NACH Credit - Salary Disbursement Batch', particulars: 'NACH Credit - Salary Disbursement Batch',
debit: 0, debit: 0,
@@ -1272,7 +1275,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'NACH', glCode: 'NACH',
voucherNo: 'NACH/2607/3305', voucherNo: 'NACH/2607/3305',
accountNo: 'NACH-SETL-CR-02', accountNo: 'UMRN-NACH26073100156',
accountName: 'NACH Credits - Government Subsidy (DBT) Disbursement (Aggregated)', accountName: 'NACH Credits - Government Subsidy (DBT) Disbursement (Aggregated)',
particulars: 'NACH Credit - Subsidy Disbursement Batch', particulars: 'NACH Credit - Subsidy Disbursement Batch',
debit: 0, debit: 0,
@@ -1281,7 +1284,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'NACH', glCode: 'NACH',
voucherNo: 'NACH/2607/3306', voucherNo: 'NACH/2607/3306',
accountNo: 'NACH-SETL-DR-01', accountNo: 'UMRN-NACH26073100198',
accountName: 'NACH Debits - Retail Loan EMI Collection (Aggregated)', accountName: 'NACH Debits - Retail Loan EMI Collection (Aggregated)',
particulars: 'NACH Debit - Loan EMI Collection Batch', particulars: 'NACH Debit - Loan EMI Collection Batch',
debit: 560000, debit: 560000,
@@ -1290,7 +1293,7 @@ export class CashClearingTransferSummaryComponent {
{ {
glCode: 'NACH', glCode: 'NACH',
voucherNo: 'NACH/2607/3307', voucherNo: 'NACH/2607/3307',
accountNo: 'NACH-SETL-DR-02', accountNo: 'UMRN-NACH26073100233',
accountName: 'NACH Debits - Gold Loan EMI Collection (Aggregated)', accountName: 'NACH Debits - Gold Loan EMI Collection (Aggregated)',
particulars: 'NACH Debit - Gold Loan EMI Collection Batch', particulars: 'NACH Debit - Gold Loan EMI Collection Batch',
debit: 305000, debit: 305000,
@@ -1298,9 +1301,14 @@ export class CashClearingTransferSummaryComponent {
} }
]; ];
/** YYMM tag baked into every dummy voucher/reference number (e.g. "2607" for Jul 2026) — kept
* in sync with the real current date so it never goes stale in a future month. */
private dateTag = '2607';
constructor() { constructor() {
const d = new Date(); const d = new Date();
this.asOnDate = `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`; this.asOnDate = `${String(d.getDate()).padStart(2, '0')}-${String(d.getMonth() + 1).padStart(2, '0')}-${d.getFullYear()}`;
this.dateTag = `${String(d.getFullYear()).slice(-2)}${String(d.getMonth() + 1).padStart(2, '0')}`;
const creds = this.auth.getCredentials(); const creds = this.auth.getCredentials();
this.isHeadOffice = this.auth.isHeadOfficeUser(); this.isHeadOffice = this.auth.isHeadOfficeUser();
@@ -1318,6 +1326,11 @@ export class CashClearingTransferSummaryComponent {
} }
} }
/** Renders a voucher/reference number with the baked-in "2607" swapped for today's real YYMM tag. */
display(value: string): string {
return (value || '').replace(/2607/g, this.dateTag);
}
private baseRowsFor(tab: TabKey): SummaryRow[] { private baseRowsFor(tab: TabKey): SummaryRow[] {
switch (tab) { switch (tab) {
case 'cash': case 'cash':
@@ -1355,15 +1368,36 @@ export class CashClearingTransferSummaryComponent {
return rows.map((r) => ({ ...r, opening: this.roundAmt(r.opening * factor) })); return rows.map((r) => ({ ...r, opening: this.roundAmt(r.opening * factor) }));
} }
private scaleTxns(txns: TxnRow[], factor: number, branchTag?: string): TxnRow[] { /**
if (factor === 1) { * Re-derives a customer account number or bank IFSC code for a different branch by shifting
* its numeric segment — NOT by tagging a prefix onto Branch 1's value, since real account
* numbers and IFSC codes have a fixed, standard shape and a "BR2-" prefix would look fake.
* Everything else (UTR/RRN/UMRN/DD/PO/TDS/GST/cheque/branch/settlement references) keeps the
* simpler branch-tag prefix, since those are already free-form internal reference formats.
*/
private scaleAccountNo(accountNo: string, branch: Branch): string {
if (/^\d{12}$/.test(accountNo)) {
const branchSeg = (parseInt(accountNo.slice(0, 3), 10) + branch.acctShift) % 1000;
return String(branchSeg).padStart(3, '0') + accountNo.slice(3);
}
const ifsc = accountNo.match(/^([A-Z]{4}0)(\d{6})$/);
if (ifsc) {
const serial = (parseInt(ifsc[2], 10) + branch.acctShift) % 1000000;
return ifsc[1] + String(serial).padStart(6, '0');
}
return `${branch.code}-${accountNo}`;
}
private scaleTxns(txns: TxnRow[], branch: Branch | null): TxnRow[] {
if (!branch || branch.scale === 1) {
return txns; return txns;
} }
return txns.map((t) => ({ return txns.map((t) => ({
...t, ...t,
voucherNo: branchTag ? `${branchTag}-${t.voucherNo}` : t.voucherNo, voucherNo: `${branch.code}-${t.voucherNo}`,
debit: this.roundAmt(t.debit * factor), accountNo: this.scaleAccountNo(t.accountNo, branch),
credit: this.roundAmt(t.credit * factor) debit: this.roundAmt(t.debit * branch.scale),
credit: this.roundAmt(t.credit * branch.scale)
})); }));
} }
@@ -1387,13 +1421,13 @@ export class CashClearingTransferSummaryComponent {
private effectiveTxns(tab: TabKey): TxnRow[] { private effectiveTxns(tab: TabKey): TxnRow[] {
const base = this.baseTxnsFor(tab); const base = this.baseTxnsFor(tab);
if (!this.isHeadOffice) { if (!this.isHeadOffice) {
return this.ownBranch ? this.scaleTxns(base, this.ownBranch.scale, this.ownBranch.code) : base; return this.scaleTxns(base, this.ownBranch);
} }
if (this.selectedBranchCode === 'ALL') { if (this.selectedBranchCode === 'ALL') {
return this.branches.flatMap((b) => this.scaleTxns(base, b.scale, b.code)); return this.branches.flatMap((b) => this.scaleTxns(base, b));
} }
const branch = this.branches.find((b) => b.code === this.selectedBranchCode); const branch = this.branches.find((b) => b.code === this.selectedBranchCode);
return this.scaleTxns(base, branch ? branch.scale : 1, branch?.code); return this.scaleTxns(base, branch ?? null);
} }
get rowsForActiveTab(): SummaryRow[] { get rowsForActiveTab(): SummaryRow[] {
@@ -1524,7 +1558,7 @@ export class CashClearingTransferSummaryComponent {
return this.branches.map((b) => { return this.branches.map((b) => {
const pairs = this.allTabs.map((tab) => ({ const pairs = this.allTabs.map((tab) => ({
rows: this.scaleRows(this.baseRowsFor(tab), b.scale), rows: this.scaleRows(this.baseRowsFor(tab), b.scale),
txns: this.scaleTxns(this.baseTxnsFor(tab), b.scale, b.code) txns: this.scaleTxns(this.baseTxnsFor(tab), b)
})); }));
const opening = pairs.reduce((sum, c) => sum + this.total(c.rows, c.txns, 'opening'), 0); 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 credit = pairs.reduce((sum, c) => sum + this.total(c.rows, c.txns, 'credit'), 0);
@@ -533,6 +533,8 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
screeningLogId: number | null = null; screeningLogId: number | null = null;
/** Snapshot of the name that was last verified — used to invalidate verification when name changes */ /** Snapshot of the name that was last verified — used to invalidate verification when name changes */
private verifiedName: string = ''; private verifiedName: string = '';
/** True while prefillForEdit() is patching historical monthly/annual values — see linkMonthlyAnnual(). */
private suppressMonthlyAnnualLink = false;
/** /**
* @param {FormBuilder} formBuilder Form Builder * @param {FormBuilder} formBuilder Form Builder
@@ -709,6 +711,12 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
const data = this.clientData; const data = this.clientData;
this.loadExistingSeniorPwdDocuments(); this.loadExistingSeniorPwdDocuments();
// Loading an existing client's independently-saved monthly/annual values must not run
// through the auto-calc in linkMonthlyAnnual() — patching monthlyIncome then annualIncome
// (or vice versa) would otherwise have each overwrite the other via its valueChanges,
// instead of keeping the exact historical values as saved.
this.suppressMonthlyAnnualLink = true;
// Patch all base controls that exist on the form // Patch all base controls that exist on the form
this.createClientForm.patchValue({ this.createClientForm.patchValue({
officeId: data.officeId, officeId: data.officeId,
@@ -743,6 +751,7 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
? new Date(data.timeline.submittedOnDate) ? new Date(data.timeline.submittedOnDate)
: this.settingsService.businessDate : this.settingsService.businessDate
}); });
this.suppressMonthlyAnnualLink = false;
// Patch name fields (already created by buildDependencies via legalFormId subscription) // Patch name fields (already created by buildDependencies via legalFormId subscription)
const legalFormId = data.legalForm?.id || LegalFormId.PERSON; const legalFormId = data.legalForm?.id || LegalFormId.PERSON;
@@ -954,6 +963,43 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
Validators.required Validators.required
] ]
}); });
this.linkMonthlyAnnual('monthlyIncome', 'annualIncome');
this.linkMonthlyAnnual('monthlyTurnover', 'annualTurnover');
}
/**
* Keeps a Monthly/Annual pair of amount fields in sync at a 12x ratio — typing into either
* one auto-fills the other. Uses { emitEvent: false } on the write side so the two
* subscriptions don't trigger each other back and forth.
*/
/** Rounds to at most 2 decimals without padding whole numbers with a trailing ".00". */
private round2(n: number): string {
return (Math.round(n * 100) / 100).toString();
}
private linkMonthlyAnnual(monthlyControlName: string, annualControlName: string): void {
const monthly = this.createClientForm.get(monthlyControlName);
const annual = this.createClientForm.get(annualControlName);
if (!monthly || !annual) {
return;
}
monthly.valueChanges.subscribe((value) => {
if (this.suppressMonthlyAnnualLink) {
return;
}
const num = parseFloat(value);
annual.setValue(!isNaN(num) && num >= 0 ? this.round2(num * 12) : '', { emitEvent: false });
});
annual.valueChanges.subscribe((value) => {
if (this.suppressMonthlyAnnualLink) {
return;
}
const num = parseFloat(value);
monthly.setValue(!isNaN(num) && num >= 0 ? this.round2(num / 12) : '', { emitEvent: false });
});
} }
/** /**