feat(savings,shares): bidirectional savings/share account linking, new Migrate Cash Denomination screen

- Savings account creation shows a required "Default Share Account" picker once Share Linking is
  enabled, populated from the client's active share accounts; submits shareAccountId alongside it.
- Fixes a real data-loss bug on Share Account creation: the Charges step re-seeded its table from
  the product's default charges on every ngOnChanges firing (e.g. stepping back to Details and
  forward again), silently discarding a charge the clerk had already added, edited, or removed - a
  pristine guard existed for exactly this but wasn't checked in ngOnChanges. Now it is.
- Removes the hard "required" on Share Account creation's Default Savings Account field, matching
  the backend now accepting a share account with no linked savings account.
- New /accounting/migrate-cash-denomination screen: counts a branch's opening cash into notes and
  coins, checks it against (or defines, if unset) the Cash GL account's balance, and opens the
  branch's first business day with that same count - the vault-side counterpart to Migrate Opening
  Balances, kept under Accounting rather than Branch Ops/Cashier so it isn't reachable by mistake.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-31 22:18:33 +05:30
co-authored by Claude Sonnet 5
parent 6624009d8c
commit 7a2cb25538
13 changed files with 830 additions and 20 deletions
+25
View File
@@ -304,6 +304,31 @@ export class CashierService {
return this.http.get('/branchday/denominations', { params });
}
/** What the office's cash-at-vault GL account currently holds — zero means the count itself will
* define its opening balance; non-zero means the count must match it exactly. */
cashGlStatus(officeId?: number): Observable<any> {
let params = new HttpParams();
if (officeId) params = params.set('officeId', String(officeId));
return this.http.get('/branchday/cash-gl-status', { params });
}
/** One-step new-institution path: defines (or reconciles against) the cash GL's opening balance
* from a counted denomination breakdown, then opens the business day with that same count. */
migrateCashDenomination(
officeId: number,
businessDate: string,
denominations: { denominationId: number; denominationValue: number; count: number }[],
openingDifferenceReason?: string
): Observable<any> {
return this.http.post('/branchday/migrate-cash-denomination', {
officeId,
businessDate,
denominations,
openingDifferenceReason: openingDifferenceReason ?? null,
locale: 'en'
});
}
/** Denomination-wise stock a vault (teller) currently holds — "how much of what does the bank have". */
vaultPosition(tellerId?: number, asOfDate?: string): Observable<any> {
let params = new HttpParams();