Files
cbsnexora/src/app/deposits/recurring-deposits/recurring-deposits-account-actions/deposit-recurring-deposits-account/deposit-recurring-deposits-account.component.html
T
Jaanu423andClaude Opus 4.7 555d9fb4b7 feat: send denominationId in deposit payload + block submit on denomination mismatch
- All 4 deposit forms (Current/Savings/FD/RD) now send denominationId +
  denominationValue in denominationBreakdown so the backend can persist rows.
- Submit is disabled when a denomination breakdown is entered but its total
  does not match the transaction amount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 18:37:19 +05:30

391 lines
16 KiB
HTML

<!--
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]="['../../general']">
<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 Recurring Deposit' | translate)
: ('labels.heading.Deposit Money To Recurring Deposit' | 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 -->
<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">Current 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>
</div>
<form [formGroup]="depositRecurringDepositForm" (ngSubmit)="submit()" class="sat__form">
<!-- LEFT: 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>
<!-- 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>
<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>
</section>
<!-- RIGHT: transaction 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)="transactionDatePicker.open()">
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
<input
matInput
[min]="minDate"
[max]="maxDate"
[matDatepicker]="transactionDatePicker"
required
formControlName="transactionDate"
/>
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
<mat-datepicker #transactionDatePicker></mat-datepicker>
@if (depositRecurringDepositForm.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]="depositRecurringDepositForm.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. 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 Id' | translate }}</mat-label>
<mat-select formControlName="paymentTypeId" required>
@for (paymentType of paymentTypes; track paymentType) {
<mat-option [value]="paymentType.id">{{ paymentType.name }}</mat-option>
}
</mat-select>
@if (depositRecurringDepositForm.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]="!!showPaymentDetails">
<span class="sat__toggle-label">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
</mat-slide-toggle>
</div>
@if (showPaymentDetails) {
<div class="sat__sub-grid">
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Account Number' | translate }}</mat-label>
<input matInput formControlName="accountNumber" />
</mat-form-field>
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
<input matInput formControlName="checkNumber" />
</mat-form-field>
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
<input matInput formControlName="routingCode" />
</mat-form-field>
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
<input matInput formControlName="receiptNumber" />
</mat-form-field>
<mat-form-field appearance="outline" class="sat__field sat__field--wide">
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
<input matInput formControlName="bankNumber" />
</mat-form-field>
</div>
}
<mat-form-field appearance="outline" class="sat__field">
<mat-label>{{ 'labels.inputs.Note' | 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]="['../../general']">
<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]="!depositRecurringDepositForm.valid || (denominationTotal > 0 && !denominationMatchesForm)"
>
<fa-icon icon="check" class="m-r-6"></fa-icon>
{{ 'labels.buttons.Submit' | translate }}
</button>
</div>
</div>
</section>
</form>
</div>