- 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>
391 lines
16 KiB
HTML
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>
|