Files
cbsnexora/src/app/savings/saving-account-actions/savings-account-transactions/savings-account-transactions.component.html
T
Jaanu423andClaude Sonnet 4.6 f291e4da5b 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>
2026-06-01 10:10:17 +05:30

528 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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/.
-->
<div class="sat" [class.sat--deposit]="transactionType.deposit" [class.sat--withdrawal]="transactionType.withdrawal">
<!-- Hero header -->
<div class="sat__hero">
<div class="sat__hero-top">
<button type="button" mat-stroked-button class="sat__hero-back" [routerLink]="['../../transactions']">
<fa-icon icon="arrow-left" class="m-r-6"></fa-icon>
{{ 'labels.buttons.Back' | translate }}
</button>
<div class="sat__hero-ico">
<fa-icon [icon]="transactionType.deposit ? 'arrow-down' : 'arrow-up'"></fa-icon>
</div>
<div class="sat__hero-title-wrap">
<h2 class="sat__title">
{{
transactionType.withdrawal
? ('labels.heading.Withdraw Money From Saving Account' | translate)
: ('labels.heading.Deposit Money To Saving Account' | translate)
}}
</h2>
<p class="sat__sub">
{{
transactionType.deposit
? 'Count the cash by denomination, then confirm the payment details.'
: 'Disburse cash by denomination and confirm the payment details.'
}}
</p>
</div>
@if (transactionType.withdrawal && clientId) {
<button
type="button"
mat-flat-button
class="sat__hero-verify"
[class.sat__hero-verify--pulse]="!identityVerified"
(click)="openIdentityVerify()"
aria-label="Verify customer identity"
>
<fa-icon icon="id-card" class="m-r-6"></fa-icon>
Verify Identity
</button>
}
<div class="sat__hero-meta">
<span class="sat__hero-meta-label">Counted Total</span>
<span class="sat__hero-meta-value"> ₹{{ denominationTotal | number: '1.2-2' }} </span>
@if (totalPieces > 0) {
<span class="sat__hero-meta-sub">{{ totalPieces }} piece(s)</span>
}
</div>
</div>
<!-- Context strip: client / account / KYC / risk / same-day NEFT-RTGS -->
<div class="sat__ctx">
<div class="sat__ctx-item">
<fa-icon icon="user" class="sat__ctx-ico"></fa-icon>
<div class="sat__ctx-text">
<span class="sat__ctx-label">Client</span>
<span class="sat__ctx-value">{{ clientName || '—' }}</span>
</div>
</div>
<div class="sat__ctx-divider"></div>
<div class="sat__ctx-item">
<fa-icon icon="hashtag" class="sat__ctx-ico"></fa-icon>
<div class="sat__ctx-text">
<span class="sat__ctx-label">Account No</span>
<span class="sat__ctx-value sat__ctx-value--mono">{{ accountNo || '—' }}</span>
</div>
</div>
<div class="sat__ctx-divider"></div>
<div class="sat__ctx-item sat__ctx-item--balance">
<fa-icon icon="wallet" class="sat__ctx-ico"></fa-icon>
<div class="sat__ctx-text">
<span class="sat__ctx-label">Available Balance</span>
<span class="sat__ctx-value sat__ctx-value--amount sat__ctx-value--lg">
₹{{ availableBalance | number: '1.2-2' }}
</span>
</div>
</div>
<div class="sat__ctx-divider"></div>
<div class="sat__ctx-item">
<fa-icon icon="id-card" class="sat__ctx-ico"></fa-icon>
<div class="sat__ctx-text">
<span class="sat__ctx-label">KYC</span>
<span
class="sat__ctx-badge"
[class.sat__ctx-badge--ok]="kycStatus === 'Verified'"
[class.sat__ctx-badge--warn]="kycStatus === 'Partial'"
[class.sat__ctx-badge--danger]="kycStatus === 'Pending'"
>
<fa-icon
[icon]="
kycStatus === 'Verified'
? 'check-circle'
: kycStatus === 'Partial'
? 'exclamation-triangle'
: 'times-circle'
"
></fa-icon>
{{ kycStatus }}
</span>
</div>
</div>
<div class="sat__ctx-divider"></div>
<div class="sat__ctx-item">
<fa-icon icon="exchange-alt" class="sat__ctx-ico"></fa-icon>
<div class="sat__ctx-text">
<span class="sat__ctx-label">Today's NEFT/RTGS</span>
<span class="sat__ctx-value sat__ctx-value--amount"> ₹{{ sameDayNeftRtgs | number: '1.2-2' }} </span>
</div>
</div>
<div class="sat__ctx-divider"></div>
<div class="sat__ctx-item">
<fa-icon icon="money-check-alt" class="sat__ctx-ico"></fa-icon>
<div class="sat__ctx-text">
<span class="sat__ctx-label">Cheque In Clearing</span>
<span class="sat__ctx-value sat__ctx-value--amount"> ₹{{ sameDayChequeClearing | number: '1.2-2' }} </span>
</div>
</div>
</div>
</div>
<form [formGroup]="savingAccountTransactionForm" (ngSubmit)="submit()" class="sat__form">
<!-- ─── LEFT COLUMN: denomination breakdown ────────────────────── -->
<section class="sat__panel sat__panel--denom">
<header class="sat__panel-head">
<fa-icon icon="money-bill-wave" class="sat__panel-ico"></fa-icon>
<h3 class="sat__panel-title">Cash Denomination Breakdown</h3>
<span class="sat__panel-spacer"></span>
@if (hasInactive) {
<button type="button" mat-stroked-button class="sat__panel-action" (click)="showInactive = !showInactive">
{{ showInactive ? 'Hide discontinued' : 'Show discontinued' }}
</button>
}
<button type="button" mat-stroked-button class="sat__panel-action" (click)="clearDenominations()">
<fa-icon icon="undo" class="m-r-6"></fa-icon> Clear
</button>
</header>
<!-- Opening cash position -->
@if (openingLoaded && openingDenoms.length > 0) {
<div class="sat__pos-card sat__pos-card--open">
<div class="sat__pos-header">
<span class="sat__pos-label">Opening Balance</span>
<span class="sat__pos-total">₹{{ openingTotal | number: '1.2-2' }}</span>
</div>
@if (openingNotes.length > 0) {
<div class="sat__pos-section">
<span class="sat__pos-section-lbl">Notes</span>
<div class="sat__pos-pills">
@for (d of openingNotes; track d.denominationId) {
<span class="sat__pos-pill"
>₹{{ d.denominationValue }}<span class="sat__pos-pill-x">×{{ d.count }}</span></span
>
}
</div>
</div>
}
@if (openingCoins.length > 0) {
<div class="sat__pos-section">
<span class="sat__pos-section-lbl sat__pos-section-lbl--coin">Coins</span>
<div class="sat__pos-pills">
@for (d of openingCoins; track d.denominationId) {
<span class="sat__pos-pill sat__pos-pill--coin"
>₹{{ d.denominationValue }}<span class="sat__pos-pill-x">×{{ d.count }}</span></span
>
}
</div>
</div>
}
</div>
}
<!-- Denomination table (Denom · Qty · Amount) -->
<div class="sat__dt">
<div class="sat__dt-head">
<span class="sat__dt-h-denom">Denom</span>
<span class="sat__dt-h-qty">Qty</span>
<span class="sat__dt-h-amount">Amount</span>
</div>
<!-- Bank notes -->
<div class="sat__dt-section">
<span class="sat__dt-section-title">Bank Notes</span>
<span class="sat__dt-section-total">₹{{ notesTotal | number: '1.2-2' }}</span>
</div>
@for (d of notes; track d.value) {
<div
class="sat__dt-row"
[class.sat__dt-row--active]="countFor(d) > 0"
[style.display]="!d.active && !showInactive ? 'none' : null"
[style.opacity]="d.active ? null : 0.55"
[attr.title]="d.active ? null : 'Discontinued — not active for new deposits'"
>
<div class="sat__dt-denom">
<span class="sat__denom-chip sat__denom-chip--note">{{ d.label }}</span>
</div>
<div class="sat__dt-qty">
<div class="sat__denom-stepper">
<button
type="button"
class="sat__denom-step"
(click)="bumpDenom(d, -1)"
[disabled]="countFor(d) === 0 || !d.active"
aria-label="Decrease"
>
−
</button>
<input
type="number"
min="0"
[value]="countFor(d)"
(input)="onDenomChange(d, $any($event.target).value)"
[disabled]="!d.active"
inputmode="numeric"
class="sat__denom-count"
/>
<button
type="button"
class="sat__denom-step"
(click)="bumpDenom(d, 1)"
[disabled]="!d.active"
aria-label="Increase"
>
+
</button>
</div>
</div>
<div class="sat__dt-amount" [class.sat__dt-amount--zero]="countFor(d) === 0">
₹{{ subtotalFor(d) | number: '1.2-2' }}
</div>
</div>
}
<!-- Coins -->
<div class="sat__dt-section">
<span class="sat__dt-section-title">Coins</span>
<span class="sat__dt-section-total">₹{{ coinsTotal | number: '1.2-2' }}</span>
</div>
@for (d of coins; track d.value) {
<div
class="sat__dt-row"
[class.sat__dt-row--active]="countFor(d) > 0"
[style.display]="!d.active && !showInactive ? 'none' : null"
[style.opacity]="d.active ? null : 0.55"
[attr.title]="d.active ? null : 'Discontinued — not active for new deposits'"
>
<div class="sat__dt-denom">
<span class="sat__denom-chip sat__denom-chip--coin sat__denom-chip--circle">{{ d.label }}</span>
</div>
<div class="sat__dt-qty">
<div class="sat__denom-stepper">
<button
type="button"
class="sat__denom-step"
(click)="bumpDenom(d, -1)"
[disabled]="countFor(d) === 0 || !d.active"
aria-label="Decrease"
>
−
</button>
<input
type="number"
min="0"
[value]="countFor(d)"
(input)="onDenomChange(d, $any($event.target).value)"
[disabled]="!d.active"
inputmode="numeric"
class="sat__denom-count"
/>
<button
type="button"
class="sat__denom-step"
(click)="bumpDenom(d, 1)"
[disabled]="!d.active"
aria-label="Increase"
>
+
</button>
</div>
</div>
<div class="sat__dt-amount" [class.sat__dt-amount--zero]="countFor(d) === 0">
₹{{ subtotalFor(d) | number: '1.2-2' }}
</div>
</div>
}
</div>
<!-- Tally + apply -->
<div class="sat__tally">
<div class="sat__tally-row">
<span class="sat__tally-label">Banknotes</span>
<span class="sat__tally-value">₹{{ notesTotal | number: '1.2-2' }}</span>
</div>
<div class="sat__tally-row">
<span class="sat__tally-label">Coins</span>
<span class="sat__tally-value">₹{{ coinsTotal | number: '1.2-2' }}</span>
</div>
<div class="sat__tally-row sat__tally-row--grand">
<span class="sat__tally-label">Grand Total</span>
<span class="sat__tally-value">₹{{ denominationTotal | number: '1.2-2' }}</span>
</div>
<button
type="button"
mat-flat-button
class="sat__apply"
[disabled]="denominationTotal === 0"
(click)="applyDenominationTotal()"
>
<fa-icon icon="arrow-right" class="m-r-6"></fa-icon>
Use ₹{{ denominationTotal | number: '1.2-2' }} as Transaction Amount
</button>
</div>
<!-- Denomination overdraft warning (withdrawal only) -->
@if (transactionType.withdrawal && hasWithdrawalDenomOverdraft) {
<div class="sat__denom-warn">
<div class="sat__denom-warn-title">
<fa-icon icon="exclamation-triangle" class="sat__denom-warn-ico"></fa-icon>
<span>Insufficient denomination stock — cannot proceed</span>
</div>
<div class="sat__denom-warn-rows">
@for (item of withdrawalDenomOverdraft; track item.label) {
<div class="sat__denom-warn-row">
<span class="sat__denom-warn-chip">{{ item.label }}</span>
<span class="sat__denom-warn-stat"
><span class="sat__denom-warn-lbl">Need</span><strong>{{ item.requested }}</strong></span
>
<span class="sat__denom-warn-dot">·</span>
<span class="sat__denom-warn-stat"
><span class="sat__denom-warn-lbl">Available</span><strong>{{ item.available }}</strong></span
>
<span class="sat__denom-warn-short">Short: {{ item.requested - item.available }}</span>
</div>
}
</div>
</div>
}
<!-- Closing cash position -->
@if (openingLoaded && openingDenoms.length > 0) {
<div
class="sat__pos-card"
[class.sat__pos-card--close]="closingTotal >= 0"
[class.sat__pos-card--neg]="closingTotal < 0"
>
<div class="sat__pos-header">
<span class="sat__pos-label"
>Closing Balance
<span class="sat__pos-label-hint"
>(after this {{ transactionType.deposit ? 'deposit' : 'withdrawal' }})</span
></span
>
<span class="sat__pos-total" [class.sat__pos-total--neg]="closingTotal < 0"
>₹{{ closingTotal | number: '1.2-2' }}</span
>
</div>
@if (denominationTotal > 0 && closingDenoms.length > 0) {
@if (closingNotes.length > 0) {
<div class="sat__pos-section">
<span class="sat__pos-section-lbl">Notes</span>
<div class="sat__pos-pills">
@for (d of closingNotes; track d.denominationId) {
<span class="sat__pos-pill" [class.sat__pos-pill--changed]="d.delta !== 0">
₹{{ d.denominationValue }}<span class="sat__pos-pill-x">×{{ d.closingCount }}</span>
</span>
}
</div>
</div>
}
@if (closingCoins.length > 0) {
<div class="sat__pos-section">
<span class="sat__pos-section-lbl sat__pos-section-lbl--coin">Coins</span>
<div class="sat__pos-pills">
@for (d of closingCoins; track d.denominationId) {
<span class="sat__pos-pill sat__pos-pill--coin" [class.sat__pos-pill--changed]="d.delta !== 0">
₹{{ d.denominationValue }}<span class="sat__pos-pill-x">×{{ d.closingCount }}</span>
</span>
}
</div>
</div>
}
}
</div>
}
</section>
<!-- ─── RIGHT COLUMN: transaction + payment details ────────────── -->
<section class="sat__panel sat__panel--side">
<header class="sat__panel-head">
<fa-icon icon="info-circle" class="sat__panel-ico"></fa-icon>
<h3 class="sat__panel-title">Transaction Details</h3>
</header>
<div class="sat__panel-body">
<mat-form-field appearance="outline" class="sat__field" (click)="dueDatePicker.open()">
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="dueDatePicker"
formControlName="transactionDate"
required
/>
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
<mat-datepicker #dueDatePicker></mat-datepicker>
@if (savingAccountTransactionForm.controls.transactionDate.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<mifosx-input-amount
[currency]="currency"
[isRequired]="true"
[inputFormControl]="savingAccountTransactionForm.controls.transactionAmount"
[inputLabel]="'Transaction Amount'"
>
</mifosx-input-amount>
@if (denominationTotal > 0 && !denominationMatchesForm) {
<div class="sat__mismatch">
<fa-icon icon="exclamation-triangle" class="sat__mismatch-ico"></fa-icon>
<span>
Counted cash <strong>₹{{ denominationTotal | number: '1.2-2' }}</strong> doesn't match the transaction
amount. Click <strong>Use ₹{{ denominationTotal | number: '1.2-2' }}</strong> to sync.
</span>
</div>
}
@if (denominationMatchesForm) {
<div class="sat__match">
<fa-icon icon="check-circle" class="sat__match-ico"></fa-icon>
Cash count matches transaction amount.
</div>
}
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Payment Type' | translate }}</mat-label>
<mat-select required formControlName="paymentTypeId">
@for (paymentType of paymentTypeOptions; track paymentType) {
<mat-option [value]="paymentType.id">{{ paymentType.name }}</mat-option>
}
</mat-select>
@if (savingAccountTransactionForm.controls.paymentTypeId.hasError('required')) {
<mat-error>
{{ 'labels.inputs.Payment Type' | translate }} {{ 'labels.commons.is' | translate }}
<strong>{{ 'labels.commons.required' | translate }}</strong>
</mat-error>
}
</mat-form-field>
<div class="sat__toggle-row" (click)="addPaymentDetails()">
<mat-slide-toggle [checked]="!!addPaymentDetailsFlag">
<span class="sat__toggle-label">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
</mat-slide-toggle>
</div>
@if (addPaymentDetailsFlag) {
<div class="sat__sub-grid">
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Account Number' | translate }}</mat-label>
<input type="number" formControlName="accountNumber" matInput />
</mat-form-field>
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
<input type="number" formControlName="checkNumber" matInput />
</mat-form-field>
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
<input formControlName="routingCode" matInput />
</mat-form-field>
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
<input formControlName="receiptNumber" matInput />
</mat-form-field>
<mat-form-field appearance="outline" class="sat__field sat__field--wide">
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
<input formControlName="bankNumber" matInput />
</mat-form-field>
</div>
}
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Notes' | translate }}</mat-label>
<textarea formControlName="note" matInput cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
</mat-form-field>
<div class="sat__actions">
<button type="button" mat-stroked-button class="sat__cancel" [routerLink]="['../../transactions']">
<fa-icon icon="times" class="m-r-6"></fa-icon>
{{ 'labels.buttons.Cancel' | translate }}
</button>
<button
mat-flat-button
type="submit"
class="sat__submit"
[disabled]="
!savingAccountTransactionForm.valid ||
(denominationTotal > 0 && !denominationMatchesForm) ||
hasWithdrawalDenomOverdraft
"
>
<fa-icon icon="check" class="m-r-6"></fa-icon>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</div>
</section>
</form>
</div>