feat(cashier): denomination overdraft validation, exchange detail fix, cash exchange in reports

- Savings/Current/FD/RD withdrawal forms: card-style overdraft warning when
  requested denomination count exceeds cashier stock; submit blocked on overdraft
- Cash History detail view: show both RECEIVED and GIVEN denomination breakdowns
  for Cash Exchange (previously only RECEIVED side was displayed)
- Closing Cash Report: add Cash Exchanges section (denomination-wise received/given/net)
- Excess Transaction Report: include cash exchanges >= threshold with Type badge
  (Exchange/Txn) and ID Proof column for RBI/AML compliance
- cashier-shared.scss: etr-badge and closing-exchange-head styles
- Cash Exchange entry: overdraft warning redesigned to cx-overdraft-card BEM layout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-01 10:10:17 +05:30
co-authored by Claude Sonnet 4.6
parent 51495f8eb8
commit f291e4da5b
24 changed files with 1339 additions and 25 deletions
@@ -22,6 +22,49 @@
</div>
</header>
<!-- ── Customer Info Bar ── -->
<div class="cx-customer-bar">
<div
class="cx-customer-toggle"
[class.cx-customer-toggle--active]="isAccountHolder"
(click)="toggleAccountHolder()"
>
<mat-icon>{{ isAccountHolder ? 'check_box' : 'check_box_outline_blank' }}</mat-icon>
<span>Account Holder</span>
</div>
@if (isAccountHolder) {
<label class="cx-field cx-field--inline">
<span>Account No.</span>
<input
type="text"
[value]="accountNo"
(input)="accountNo = $any($event.target).value"
placeholder="e.g. 000101234567"
/>
</label>
} @else {
<span class="cx-walk-in"><mat-icon>person_outline</mat-icon> Walk-in Customer</span>
}
@if (needsIdProof) {
<div class="cx-pan-required">
<mat-icon>warning</mat-icon>
<span>Amount ≥ ₹50,000 — Customer PAN / Aadhaar required (RBI)</span>
</div>
<label class="cx-field cx-field--inline cx-field--pan">
<span>PAN / Aadhaar *</span>
<input
type="text"
[value]="customerIdProof"
(input)="customerIdProof = $any($event.target).value"
placeholder="ABCDE1234F"
maxlength="20"
/>
</label>
}
</div>
<div class="cx-cols">
<!-- RECEIVED -->
<section class="cx-col cx-col--received">
@@ -147,14 +190,42 @@
@if (receivedTotal > 0 && !matches) {
<div class="cx-warn"><mat-icon>warning</mat-icon> Received and Given totals must match (net ₹0).</div>
}
@if (hasGivenOverdraft) {
<div class="cx-overdraft-card">
<div class="cx-overdraft-card__title">
<mat-icon class="cx-overdraft-card__ico">block</mat-icon>
<span>Insufficient stock — cannot give to customer</span>
</div>
<div class="cx-overdraft-card__rows">
@for (item of givenOverdraftItems; track item.label) {
<div class="cx-overdraft-card__row">
<span class="cx-overdraft-card__chip">{{ item.label }}</span>
<span class="cx-overdraft-card__stat"
><span class="cx-overdraft-card__lbl">Need</span><strong>{{ item.requested }}</strong></span
>
<span class="cx-overdraft-card__dot">·</span>
<span class="cx-overdraft-card__stat"
><span class="cx-overdraft-card__lbl">Available</span><strong>{{ item.available }}</strong></span
>
<span class="cx-overdraft-card__short">Short: {{ item.requested - item.available }}</span>
</div>
}
</div>
</div>
}
@if (needsIdProof && !customerIdProof.trim()) {
<div class="cx-warn cx-warn--pan">
<mat-icon>badge</mat-icon> Enter customer PAN / Aadhaar to proceed (amount ≥ ₹50,000).
</div>
}
<div class="cx-actions">
<label class="cx-field cx-field--note">
<span>Note</span>
<input type="text" [value]="note" (input)="note = $any($event.target).value" placeholder="Optional" />
</label>
<button type="button" class="cx-btn cx-btn--primary" [disabled]="!matches || saving" (click)="submit()">
<mat-icon>swap_horiz</mat-icon> Confirm Exchange
<button type="button" class="cx-btn cx-btn--primary" [disabled]="!valid || saving" (click)="submit()">
<mat-icon>swap_horiz</mat-icon> {{ saving ? 'Saving…' : 'Confirm Exchange' }}
</button>
</div>
</div>