feat: sat design for current account, FD, RD deposit/withdrawal pages
- Rebuilt current account deposit/withdrawal with sat two-column layout - Applied same sat design to FD cash transaction and RD deposit/withdrawal - Added Deposit action to FD: button config, actions dict, doAction routing - Both FD Deposit and Withdrawal now render FixedDepositsCashTransaction Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
547cfd581e
commit
1779591b7a
+334
-97
@@ -6,127 +6,364 @@
|
|||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="container mat-elevation-z8">
|
<div class="sat" [class.sat--deposit]="transactionType.deposit" [class.sat--withdrawal]="transactionType.withdrawal">
|
||||||
<mat-card>
|
<!-- Hero header -->
|
||||||
@if (transactionType.withdrawal) {
|
<div class="sat__hero">
|
||||||
<mat-card-title class="form-heading">
|
<div class="sat__hero-top">
|
||||||
{{ 'labels.heading.Withdraw Money From Current Account' | translate }}
|
<button type="button" mat-stroked-button class="sat__hero-back" [routerLink]="['../../transactions']">
|
||||||
</mat-card-title>
|
<fa-icon icon="arrow-left" class="m-r-6"></fa-icon>
|
||||||
}
|
{{ 'labels.buttons.Back' | translate }}
|
||||||
@if (transactionType.deposit) {
|
</button>
|
||||||
<mat-card-title class="form-heading">
|
<div class="sat__hero-ico">
|
||||||
{{ 'labels.heading.Deposit Money To Current Account' | translate }}
|
<fa-icon [icon]="transactionType.deposit ? 'arrow-down' : 'arrow-up'"></fa-icon>
|
||||||
</mat-card-title>
|
</div>
|
||||||
}
|
<div class="sat__hero-title-wrap">
|
||||||
|
<h2 class="sat__title">
|
||||||
|
{{
|
||||||
|
transactionType.withdrawal
|
||||||
|
? ('labels.heading.Withdraw Money From Current Account' | translate)
|
||||||
|
: ('labels.heading.Deposit Money To Current 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>
|
||||||
|
|
||||||
<form [formGroup]="currentAccountTransactionForm" (ngSubmit)="submit()">
|
<!-- Context strip: client / account / KYC / risk / same-day NEFT-RTGS -->
|
||||||
<mat-card-content>
|
<div class="sat__ctx">
|
||||||
<div class="layout-column">
|
<div class="sat__ctx-item">
|
||||||
<mat-form-field (click)="dueDatePicker.open()">
|
<fa-icon icon="user" class="sat__ctx-ico"></fa-icon>
|
||||||
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
|
<div class="sat__ctx-text">
|
||||||
<input
|
<span class="sat__ctx-label">Client</span>
|
||||||
matInput
|
<span class="sat__ctx-value">{{ clientName || '—' }}</span>
|
||||||
[min]="minDate"
|
</div>
|
||||||
[max]="maxDate"
|
</div>
|
||||||
[matDatepicker]="dueDatePicker"
|
|
||||||
formControlName="transactionDate"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
|
|
||||||
<mat-datepicker #dueDatePicker></mat-datepicker>
|
|
||||||
@if (currentAccountTransactionForm.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
|
<div class="sat__ctx-divider"></div>
|
||||||
[currency]="currency"
|
|
||||||
[isRequired]="true"
|
<div class="sat__ctx-item">
|
||||||
[inputFormControl]="currentAccountTransactionForm.controls.transactionAmount"
|
<fa-icon icon="hashtag" class="sat__ctx-ico"></fa-icon>
|
||||||
[inputLabel]="'Transaction Amount'"
|
<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'"
|
||||||
>
|
>
|
||||||
</mifosx-input-amount>
|
<fa-icon
|
||||||
|
[icon]="
|
||||||
|
kycStatus === 'Verified'
|
||||||
|
? 'check-circle'
|
||||||
|
: kycStatus === 'Partial'
|
||||||
|
? 'exclamation-triangle'
|
||||||
|
: 'times-circle'
|
||||||
|
"
|
||||||
|
></fa-icon>
|
||||||
|
{{ kycStatus }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-form-field>
|
<div class="sat__ctx-divider"></div>
|
||||||
<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 (currentAccountTransactionForm.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="flex-fill">
|
<div class="sat__ctx-item">
|
||||||
<span class="expandcollapsebutton m-l-10 m-t-40 flex-75" (click)="addPaymentDetails()">
|
<fa-icon icon="exchange-alt" class="sat__ctx-ico"></fa-icon>
|
||||||
<mat-slide-toggle>
|
<div class="sat__ctx-text">
|
||||||
<div [className]="addPaymentDetailsFlag ? 'enabled' : 'disabled'">
|
<span class="sat__ctx-label">Today's NEFT/RTGS</span>
|
||||||
<span class="m-l-10">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
|
<span class="sat__ctx-value sat__ctx-value--amount"> ₹{{ sameDayNeftRtgs | number: '1.2-2' }} </span>
|
||||||
</div>
|
</div>
|
||||||
</mat-slide-toggle>
|
</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]="currentAccountTransactionForm" (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>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- Banknotes -->
|
||||||
|
<div class="sat__denom-group">
|
||||||
|
<div class="sat__denom-group-head">
|
||||||
|
<span class="sat__denom-group-title">Banknotes</span>
|
||||||
|
<span class="sat__denom-group-total">₹{{ notesTotal | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-grid">
|
||||||
|
@for (d of notes; track d.value) {
|
||||||
|
<div class="sat__denom" [class.sat__denom--active]="countFor(d) > 0">
|
||||||
|
<div class="sat__denom-top">
|
||||||
|
<span class="sat__denom-chip sat__denom-chip--note">{{ d.label }}</span>
|
||||||
|
<span class="sat__denom-amount">₹{{ subtotalFor(d) | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-stepper">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="sat__denom-step"
|
||||||
|
(click)="bumpDenom(d, -1)"
|
||||||
|
[disabled]="countFor(d) === 0"
|
||||||
|
aria-label="Decrease"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
[value]="countFor(d)"
|
||||||
|
(input)="onDenomChange(d, $any($event.target).value)"
|
||||||
|
inputmode="numeric"
|
||||||
|
class="sat__denom-count"
|
||||||
|
/>
|
||||||
|
<button type="button" class="sat__denom-step" (click)="bumpDenom(d, 1)" aria-label="Increase">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Coins -->
|
||||||
|
<div class="sat__denom-group">
|
||||||
|
<div class="sat__denom-group-head">
|
||||||
|
<span class="sat__denom-group-title">Coins</span>
|
||||||
|
<span class="sat__denom-group-total">₹{{ coinsTotal | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-grid">
|
||||||
|
@for (d of coins; track d.value) {
|
||||||
|
<div class="sat__denom" [class.sat__denom--active]="countFor(d) > 0">
|
||||||
|
<div class="sat__denom-top">
|
||||||
|
<span class="sat__denom-chip sat__denom-chip--coin">{{ d.label }}</span>
|
||||||
|
<span class="sat__denom-amount">₹{{ subtotalFor(d) | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-stepper">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="sat__denom-step"
|
||||||
|
(click)="bumpDenom(d, -1)"
|
||||||
|
[disabled]="countFor(d) === 0"
|
||||||
|
aria-label="Decrease"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
[value]="countFor(d)"
|
||||||
|
(input)="onDenomChange(d, $any($event.target).value)"
|
||||||
|
inputmode="numeric"
|
||||||
|
class="sat__denom-count"
|
||||||
|
/>
|
||||||
|
<button type="button" class="sat__denom-step" (click)="bumpDenom(d, 1)" aria-label="Increase">+</button>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
|
</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 (currentAccountTransactionForm.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]="currentAccountTransactionForm.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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
@if (denominationMatchesForm) {
|
||||||
|
<div class="sat__match">
|
||||||
|
<fa-icon icon="check-circle" class="sat__match-ico"></fa-icon>
|
||||||
|
Cash count matches transaction amount.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<mat-form-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 (currentAccountTransactionForm.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>
|
<mat-label>{{ 'labels.inputs.Account Number' | translate }}</mat-label>
|
||||||
<input type="number" formControlName="accountNumber" matInput />
|
<input type="number" formControlName="accountNumber" matInput />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
|
||||||
<input type="number" formControlName="checkNumber" matInput />
|
<input type="number" formControlName="checkNumber" matInput />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
|
||||||
<input formControlName="routingCode" matInput />
|
<input formControlName="routingCode" matInput />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
|
||||||
<input formControlName="receiptNumber" matInput />
|
<input formControlName="receiptNumber" matInput />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
<mat-form-field appearance="outline" class="sat__field sat__field--wide">
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
|
||||||
<input formControlName="bankNumber" matInput />
|
<input formControlName="bankNumber" matInput />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<mat-label>{{ 'labels.inputs.Notes' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Notes' | translate }}</mat-label>
|
||||||
<textarea formControlName="note" matInput cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
<textarea formControlName="note" matInput cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-card-actions class="layout-row layout-xs-column layout-align-center gap-5px">
|
<div class="sat__actions">
|
||||||
<button type="button" mat-raised-button [routerLink]="['../../transactions']">
|
<button type="button" mat-stroked-button class="sat__cancel" [routerLink]="['../../transactions']">
|
||||||
{{ 'labels.buttons.Cancel' | translate }}
|
<fa-icon icon="times" class="m-r-6"></fa-icon>
|
||||||
</button>
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
<button mat-raised-button color="primary" [disabled]="!currentAccountTransactionForm.valid">
|
</button>
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
<button mat-flat-button type="submit" class="sat__submit" [disabled]="!currentAccountTransactionForm.valid">
|
||||||
</button>
|
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||||
</mat-card-actions>
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-card-content>
|
</div>
|
||||||
</form>
|
</section>
|
||||||
</mat-card>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+828
-10
@@ -6,24 +6,842 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.expandcollapsebutton {
|
/* Banking-grade transaction-entry screen. Two-column layout: cash
|
||||||
margin-top: -7px;
|
denomination breakdown on the left (the cashier's primary workspace),
|
||||||
|
transaction & payment details on the right. Hero header is blue for
|
||||||
|
deposits / teal for withdrawals. */
|
||||||
|
|
||||||
|
$navy: #0d1b2a;
|
||||||
|
$slate: #475569;
|
||||||
|
$muted: #64748b;
|
||||||
|
$border: #e2e8f0;
|
||||||
|
$soft: #f8fafc;
|
||||||
|
$blue: #1e40af;
|
||||||
|
$green: #059669;
|
||||||
|
$green-d: #047857;
|
||||||
|
$amber: #d97706;
|
||||||
|
$amber-d: #b45309;
|
||||||
|
$red: #b91c1c;
|
||||||
|
|
||||||
|
.sat {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 16px 18px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
/* ─── Hero ─────────────────────────────────────────────────────────── */
|
||||||
max-width: 37rem;
|
|
||||||
|
.sat__hero {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 16px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 45%, #1e40af 100%);
|
||||||
|
box-shadow: 0 6px 18px -10px rgb(15 23 42 / 50%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at top right, rgb(255 255 255 / 12%), transparent 55%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-input {
|
.sat__hero-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-back {
|
||||||
|
height: 30px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 0 11px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
background: rgb(255 255 255 / 14%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: 1px solid rgb(255 255 255 / 28%) !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition:
|
||||||
|
background 0.15s ease,
|
||||||
|
border-color 0.15s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgb(255 255 255 / 26%) !important;
|
||||||
|
border-color: rgb(255 255 255 / 42%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-title-wrap {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__hero {
|
||||||
|
background: linear-gradient(135deg, #134e4a 0%, #115e59 45%, #0f766e 100%);
|
||||||
|
box-shadow: 0 6px 18px -10px rgb(15 118 110 / 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-ico {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 28%);
|
||||||
|
color: #fff;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__sub {
|
||||||
|
margin: 1px 0 0;
|
||||||
|
font-size: 11.5px;
|
||||||
|
color: rgb(255 255 255 / 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-verify {
|
||||||
|
position: relative;
|
||||||
|
height: 30px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 0 12px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
color: #fff !important;
|
||||||
|
background: rgb(255 255 255 / 16%) !important;
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%) !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition:
|
||||||
|
background 0.15s ease,
|
||||||
|
border-color 0.15s ease;
|
||||||
|
overflow: visible !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgb(255 255 255 / 28%) !important;
|
||||||
|
border-color: rgb(255 255 255 / 42%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -2px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid rgb(255 255 255 / 70%);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--pulse::before {
|
||||||
|
animation: sat-verify-pulse 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--pulse:hover::before {
|
||||||
|
animation-play-state: paused;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sat-verify-pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(1.18);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1.18);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.sat__hero-verify--pulse::before {
|
||||||
|
animation: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
min-width: 130px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-label {
|
||||||
|
font-size: 9.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: rgb(255 255 255 / 78%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-sub {
|
||||||
|
font-size: 10px;
|
||||||
|
color: rgb(255 255 255 / 75%);
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Context strip ─────────────────────────────────────────────────── */
|
||||||
|
.sat__ctx {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgb(255 255 255 / 12%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 22%);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1 1 110px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-ico {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 25%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-label {
|
||||||
|
font-size: 9px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: rgb(255 255 255 / 70%);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-value {
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&--mono {
|
||||||
|
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
||||||
|
font-size: 10.5px;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--amount {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--lg {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item--balance {
|
||||||
|
background: rgb(255 255 255 / 12%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 22%);
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-divider {
|
||||||
|
width: 1px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
margin: 4px 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (width <= 720px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 9px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%);
|
||||||
|
color: #fff;
|
||||||
|
align-self: flex-start;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
fa-icon {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--ok {
|
||||||
|
background: rgb(16 185 129 / 36%);
|
||||||
|
border-color: rgb(110 231 183 / 60%);
|
||||||
|
color: #d1fae5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warn {
|
||||||
|
background: rgb(245 158 11 / 36%);
|
||||||
|
border-color: rgb(252 211 77 / 60%);
|
||||||
|
color: #fef3c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--danger {
|
||||||
|
background: rgb(239 68 68 / 36%);
|
||||||
|
border-color: rgb(252 165 165 / 60%);
|
||||||
|
color: #fee2e2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Two-column layout ───────────────────────────────────────────── */
|
||||||
|
.sat__form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
|
||||||
|
gap: 14px;
|
||||||
|
align-items: start;
|
||||||
|
|
||||||
|
@media (width <= 1100px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
background: $soft;
|
||||||
|
border-bottom: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-ico {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__panel-ico {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-action {
|
||||||
|
height: 26px !important;
|
||||||
|
border-radius: 7px !important;
|
||||||
|
font-size: 11.5px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
padding: 0 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-body {
|
||||||
|
padding: 12px 14px 14px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Denomination groups ─────────────────────────────────────────── */
|
||||||
|
.sat__denom-group {
|
||||||
|
padding: 8px 14px 4px;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-title {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: $slate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-total {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 6px 9px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s ease,
|
||||||
|
background 0.15s ease,
|
||||||
|
box-shadow 0.15s ease;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
border-color: #86efac;
|
||||||
|
background: #f0fdf4;
|
||||||
|
box-shadow: 0 1px 3px -1px rgb(4 120 87 / 18%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 48px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 9px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&--note {
|
||||||
|
background: linear-gradient(135deg, #ecfeff 0%, #cffafe 100%);
|
||||||
|
color: #155e75;
|
||||||
|
border: 1px solid #a5f3fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--coin {
|
||||||
|
background: linear-gradient(135deg, #fef9c3 0%, #fde68a 100%);
|
||||||
|
color: #854d0e;
|
||||||
|
border: 1px solid #fcd34d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-amount {
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-label {
|
.sat__denom-stepper {
|
||||||
padding-right: 25px !important;
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid $border;
|
||||||
|
background: #fff;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-heading {
|
.sat__denom-count {
|
||||||
font-size: 1.25rem;
|
flex: 1 1 auto;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
padding: 0 4px;
|
||||||
|
appearance: textfield;
|
||||||
|
|
||||||
|
&::-webkit-outer-spin-button,
|
||||||
|
&::-webkit-inner-spin-button {
|
||||||
|
appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-step {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 28px;
|
||||||
|
background: $soft;
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid $border;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $slate;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background 0.12s ease,
|
||||||
|
color 0.12s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
border-left: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: $navy;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active:not(:disabled) {
|
||||||
|
background: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Tally / apply ───────────────────────────────────────────────── */
|
||||||
|
.sat__tally {
|
||||||
|
margin: 8px 14px 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
border: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $slate;
|
||||||
|
|
||||||
|
&--grand {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px dashed #cbd5e1;
|
||||||
|
|
||||||
|
.sat__tally-label {
|
||||||
|
font-weight: 800;
|
||||||
|
color: $navy;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-value {
|
||||||
|
font-size: 15.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $green-d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__tally-row--grand .sat__tally-value {
|
||||||
|
color: $amber-d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-value {
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__apply {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
height: 34px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
box-shadow: 0 3px 10px -2px rgb(4 120 87 / 35%) !important;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
filter: grayscale(0.4);
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__apply {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
|
||||||
|
box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Form fields (right panel) ───────────────────────────────────── */
|
||||||
|
.sat__field {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-text-field-wrapper {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-form-field-infix {
|
||||||
|
min-height: 48px;
|
||||||
|
padding-top: 12px !important;
|
||||||
|
padding-bottom: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mdc-notched-outline__leading,
|
||||||
|
::ng-deep .mdc-notched-outline__notch,
|
||||||
|
::ng-deep .mdc-notched-outline__trailing {
|
||||||
|
border-color: #cbd5e1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__sub-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px 12px;
|
||||||
|
|
||||||
|
.sat__field--wide {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 600px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__toggle-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 4px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.sat__toggle-label {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $slate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Match / mismatch banners */
|
||||||
|
.sat__mismatch,
|
||||||
|
.sat__match {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12.5px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0.5rem;
|
margin: 2px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__mismatch {
|
||||||
|
background: #fffbeb;
|
||||||
|
border: 1px solid #fde68a;
|
||||||
|
color: #92400e;
|
||||||
|
|
||||||
|
&-ico {
|
||||||
|
color: $amber;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__match {
|
||||||
|
background: #ecfdf5;
|
||||||
|
border: 1px solid #a7f3d0;
|
||||||
|
color: #065f46;
|
||||||
|
|
||||||
|
&-ico {
|
||||||
|
color: $green;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Actions ─────────────────────────────────────────────────────── */
|
||||||
|
.sat__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 42px !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
padding: 0 22px !important;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__cancel {
|
||||||
|
color: $slate !important;
|
||||||
|
border-color: $border !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__submit {
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
filter: grayscale(0.3);
|
||||||
|
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__submit {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
|
||||||
|
box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-r-6 {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ─── Responsive ──────────────────────────────────────────────────── */
|
||||||
|
@media (width <= 720px) {
|
||||||
|
.sat__hero-top {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item {
|
||||||
|
flex: 1 1 calc(50% - 14px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 480px) {
|
||||||
|
.sat__ctx-item {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__actions {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+242
-19
@@ -19,16 +19,28 @@ import { Router, ActivatedRoute, RouterLink } from '@angular/router';
|
|||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { CurrentAccountsService } from '../../current-accounts.service';
|
import { CurrentAccountsService } from '../../current-accounts.service';
|
||||||
|
import { ClientsService } from 'app/clients/clients.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
import { Currency } from 'app/shared/models/general.model';
|
import { Currency } from 'app/shared/models/general.model';
|
||||||
import { InputAmountComponent } from '../../../shared/input-amount/input-amount.component';
|
import { InputAmountComponent } from '../../../shared/input-amount/input-amount.component';
|
||||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { TransactionConfirmDialogComponent } from '../../../savings/saving-account-actions/savings-account-transactions/transaction-confirm-dialog/transaction-confirm-dialog.component';
|
||||||
|
import { IdentityVerifyDialogComponent } from '../../../savings/saving-account-actions/savings-account-transactions/identity-verify-dialog/identity-verify-dialog.component';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
/** Single Indian denomination row (note or coin). */
|
||||||
|
interface Denomination {
|
||||||
|
value: number;
|
||||||
|
kind: 'note' | 'coin';
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create current account transactions component.
|
* Create current account transactions component (Deposit / Withdraw).
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-current-account-transactions',
|
selector: 'mifosx-current-account-transactions',
|
||||||
@@ -38,7 +50,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
InputAmountComponent,
|
InputAmountComponent,
|
||||||
MatSlideToggle,
|
MatSlideToggle,
|
||||||
CdkTextareaAutosize
|
CdkTextareaAutosize,
|
||||||
|
FaIconComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CurrentAccountTransactionsComponent implements OnInit {
|
export class CurrentAccountTransactionsComponent implements OnInit {
|
||||||
@@ -47,7 +60,9 @@ export class CurrentAccountTransactionsComponent implements OnInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
private currentAccountsService = inject(CurrentAccountsService);
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
|
private clientsService = inject(ClientsService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
|
||||||
/** Minimum Due Date allowed. */
|
/** Minimum Due Date allowed. */
|
||||||
minDate = new Date(2000, 0, 1);
|
minDate = new Date(2000, 0, 1);
|
||||||
@@ -73,15 +88,38 @@ export class CurrentAccountTransactionsComponent implements OnInit {
|
|||||||
currentAccountId: string;
|
currentAccountId: string;
|
||||||
currency: Currency | null = null;
|
currency: Currency | null = null;
|
||||||
|
|
||||||
/**
|
/** Indian banknote denominations (descending). */
|
||||||
* Retrieves the Current Account transaction template data from `resolve`.
|
readonly notes: Denomination[] = [
|
||||||
* @param {FormBuilder} formBuilder Form Builder.
|
{ value: 500, kind: 'note', label: '₹500' },
|
||||||
* @param {CurrentAccountsService} currentAccountsService Current Accounts Service.
|
{ value: 200, kind: 'note', label: '₹200' },
|
||||||
* @param {ActivatedRoute} route Activated Route.
|
{ value: 100, kind: 'note', label: '₹100' },
|
||||||
* @param {Dates} dateUtils Date Utils.
|
{ value: 50, kind: 'note', label: '₹50' },
|
||||||
* @param {Router} router Router for navigation.
|
{ value: 20, kind: 'note', label: '₹20' },
|
||||||
* @param {SettingsService} settingsService Settings Service
|
{ value: 10, kind: 'note', label: '₹10' }
|
||||||
*/
|
];
|
||||||
|
|
||||||
|
/** Indian coin denominations (descending). */
|
||||||
|
readonly coins: Denomination[] = [
|
||||||
|
{ value: 20, kind: 'coin', label: '₹20' },
|
||||||
|
{ value: 10, kind: 'coin', label: '₹10' },
|
||||||
|
{ value: 5, kind: 'coin', label: '₹5' },
|
||||||
|
{ value: 2, kind: 'coin', label: '₹2' },
|
||||||
|
{ value: 1, kind: 'coin', label: 'Re 1' }
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Quantity per denomination — keyed by `${kind}:${value}`. */
|
||||||
|
denominationCounts: Record<string, number> = {};
|
||||||
|
|
||||||
|
// ── Context strip data ──────────────────────────────────────────────
|
||||||
|
accountNo: string | null = null;
|
||||||
|
clientName: string | null = null;
|
||||||
|
clientId: number | null = null;
|
||||||
|
kycStatus: 'Verified' | 'Partial' | 'Pending' = 'Pending';
|
||||||
|
sameDayNeftRtgs = 0;
|
||||||
|
sameDayChequeClearing = 0;
|
||||||
|
availableBalance = 0;
|
||||||
|
identityVerified = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
|
this.route.data.subscribe((data: { currentAccountActionData: any }) => {
|
||||||
this.paymentTypeOptions = data.currentAccountActionData.paymentTypeOptions;
|
this.paymentTypeOptions = data.currentAccountActionData.paymentTypeOptions;
|
||||||
@@ -94,17 +132,93 @@ export class CurrentAccountTransactionsComponent implements OnInit {
|
|||||||
this.currentAccountId = this.route.snapshot.params['currentAccountId'];
|
this.currentAccountId = this.route.snapshot.params['currentAccountId'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the Current Account account transaction form when component loads.
|
|
||||||
*/
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.maxDate = this.settingsService.businessDate;
|
this.maxDate = this.settingsService.businessDate;
|
||||||
this.createCurrentAccountTransactionForm();
|
this.createCurrentAccountTransactionForm();
|
||||||
|
this.loadContextStrip();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadContextStrip(): void {
|
||||||
|
if (!this.currentAccountId) return;
|
||||||
|
this.currentAccountsService.getCurrentAccountData(this.currentAccountId).subscribe({
|
||||||
|
next: (account: any) => {
|
||||||
|
this.accountNo = account?.accountNo ?? null;
|
||||||
|
this.clientName = account?.clientName ?? account?.groupName ?? null;
|
||||||
|
this.clientId = account?.clientId ?? null;
|
||||||
|
if (!this.currency && account?.currency) this.currency = account.currency;
|
||||||
|
this.availableBalance = Number(account?.summary?.availableBalance ?? account?.summary?.accountBalance ?? 0);
|
||||||
|
this.computeSameDayNeftRtgs(account?.transactions);
|
||||||
|
this.computeSameDayChequeClearing(account?.transactions);
|
||||||
|
if (account?.clientId) {
|
||||||
|
this.fetchClientKyc(String(account.clientId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchClientKyc(clientId: string): void {
|
||||||
|
this.clientsService.getClientKyc(clientId).subscribe({
|
||||||
|
next: (kyc: any) => {
|
||||||
|
const pan = !!kyc?.panVerified;
|
||||||
|
const aadhaar = !!kyc?.aadhaarVerified;
|
||||||
|
this.kycStatus = pan && aadhaar ? 'Verified' : pan || aadhaar ? 'Partial' : 'Pending';
|
||||||
|
},
|
||||||
|
error: () => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeSameDayNeftRtgs(transactions: any[] | undefined): void {
|
||||||
|
if (!Array.isArray(transactions) || transactions.length === 0) {
|
||||||
|
this.sameDayNeftRtgs = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const today = this.settingsService.businessDate ?? new Date();
|
||||||
|
const sameDay = (d: any) => {
|
||||||
|
if (!d) return false;
|
||||||
|
const t = Array.isArray(d) && d.length >= 3 ? new Date(d[0], d[1] - 1, d[2]) : new Date(d);
|
||||||
|
return (
|
||||||
|
t.getFullYear() === today.getFullYear() && t.getMonth() === today.getMonth() && t.getDate() === today.getDate()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const isNeftRtgs = (t: any) => {
|
||||||
|
const txnType = (t?.transactionType?.value ?? t?.transactionType?.code ?? '').toUpperCase();
|
||||||
|
const payType = (t?.paymentDetailData?.paymentType?.name ?? '').toUpperCase();
|
||||||
|
return /NEFT|RTGS/.test(txnType) || /NEFT|RTGS/.test(payType);
|
||||||
|
};
|
||||||
|
this.sameDayNeftRtgs = transactions
|
||||||
|
.filter((t: any) => !t.reversed && sameDay(t.date) && isNeftRtgs(t))
|
||||||
|
.reduce((sum, t: any) => sum + (Number(t.amount) || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private computeSameDayChequeClearing(transactions: any[] | undefined): void {
|
||||||
|
if (!Array.isArray(transactions) || transactions.length === 0) {
|
||||||
|
this.sameDayChequeClearing = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const today = this.settingsService.businessDate ?? new Date();
|
||||||
|
const sameDay = (d: any) => {
|
||||||
|
if (!d) return false;
|
||||||
|
const t = Array.isArray(d) && d.length >= 3 ? new Date(d[0], d[1] - 1, d[2]) : new Date(d);
|
||||||
|
return (
|
||||||
|
t.getFullYear() === today.getFullYear() && t.getMonth() === today.getMonth() && t.getDate() === today.getDate()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const isCheque = (t: any) => {
|
||||||
|
const txnType = (t?.transactionType?.value ?? t?.transactionType?.code ?? '').toUpperCase();
|
||||||
|
const payType = (t?.paymentDetailData?.paymentType?.name ?? '').toUpperCase();
|
||||||
|
const hasCheckNo = !!t?.paymentDetailData?.checkNumber;
|
||||||
|
return hasCheckNo || /CHEQUE|CHECK/.test(txnType) || /CHEQUE|CHECK/.test(payType);
|
||||||
|
};
|
||||||
|
const isPending = (t: any) => {
|
||||||
|
const cleared = t?.transactionType?.bankTransfer === true || t?.transactionType?.deposit === true;
|
||||||
|
return !cleared;
|
||||||
|
};
|
||||||
|
this.sameDayChequeClearing = transactions
|
||||||
|
.filter((t: any) => !t.reversed && sameDay(t.date) && isCheque(t) && isPending(t))
|
||||||
|
.reduce((sum, t: any) => sum + (Number(t.amount) || 0), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to create the Current Account Transaction Form.
|
|
||||||
*/
|
|
||||||
createCurrentAccountTransactionForm() {
|
createCurrentAccountTransactionForm() {
|
||||||
this.currentAccountTransactionForm = this.formBuilder.group({
|
this.currentAccountTransactionForm = this.formBuilder.group({
|
||||||
transactionDate: [
|
transactionDate: [
|
||||||
@@ -123,6 +237,75 @@ export class CurrentAccountTransactionsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Denomination helpers ────────────────────────────────────────────
|
||||||
|
denomKey(d: Denomination): string {
|
||||||
|
return `${d.kind}:${d.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
countFor(d: Denomination): number {
|
||||||
|
return this.denominationCounts[this.denomKey(d)] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subtotalFor(d: Denomination): number {
|
||||||
|
return this.countFor(d) * d.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDenomChange(d: Denomination, raw: any): void {
|
||||||
|
const n = Math.max(0, Math.floor(Number(raw) || 0));
|
||||||
|
this.denominationCounts[this.denomKey(d)] = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
bumpDenom(d: Denomination, delta: number): void {
|
||||||
|
const next = Math.max(0, this.countFor(d) + delta);
|
||||||
|
this.denominationCounts[this.denomKey(d)] = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
get notesTotal(): number {
|
||||||
|
return this.notes.reduce((s, d) => s + this.subtotalFor(d), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get coinsTotal(): number {
|
||||||
|
return this.coins.reduce((s, d) => s + this.subtotalFor(d), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get denominationTotal(): number {
|
||||||
|
return this.notesTotal + this.coinsTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalPieces(): number {
|
||||||
|
return Object.values(this.denominationCounts).reduce((s, n) => s + (n || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get denominationMatchesForm(): boolean {
|
||||||
|
const formAmt = Number(this.currentAccountTransactionForm?.controls?.['transactionAmount']?.value || 0);
|
||||||
|
return this.denominationTotal > 0 && formAmt === this.denominationTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyDenominationTotal(): void {
|
||||||
|
this.currentAccountTransactionForm.controls['transactionAmount'].setValue(this.denominationTotal);
|
||||||
|
this.currentAccountTransactionForm.controls['transactionAmount'].markAsDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearDenominations(): void {
|
||||||
|
this.denominationCounts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
openIdentityVerify(): void {
|
||||||
|
if (!this.clientId) return;
|
||||||
|
this.identityVerified = true;
|
||||||
|
this.dialog.open(IdentityVerifyDialogComponent, {
|
||||||
|
width: '500px',
|
||||||
|
maxWidth: '92vw',
|
||||||
|
panelClass: 'idv-panel',
|
||||||
|
autoFocus: false,
|
||||||
|
data: {
|
||||||
|
clientId: this.clientId,
|
||||||
|
clientName: this.clientName,
|
||||||
|
accountNo: this.accountNo
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to add payment detail fields to the UI.
|
* Method to add payment detail fields to the UI.
|
||||||
*/
|
*/
|
||||||
@@ -144,9 +327,35 @@ export class CurrentAccountTransactionsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to submit the transaction details.
|
* Opens a confirmation dialog then posts the transaction.
|
||||||
*/
|
*/
|
||||||
submit() {
|
submit() {
|
||||||
|
const formAmount = Number(this.currentAccountTransactionForm.value.transactionAmount) || 0;
|
||||||
|
const paymentTypeId = this.currentAccountTransactionForm.value.paymentTypeId;
|
||||||
|
const paymentTypeLabel = this.paymentTypeOptions?.find((p) => p.id === paymentTypeId)?.name ?? null;
|
||||||
|
|
||||||
|
const ref = this.dialog.open(TransactionConfirmDialogComponent, {
|
||||||
|
width: '480px',
|
||||||
|
maxWidth: '92vw',
|
||||||
|
panelClass: 'tcd-panel',
|
||||||
|
autoFocus: false,
|
||||||
|
data: {
|
||||||
|
kind: this.transactionType.deposit ? 'deposit' : 'withdrawal',
|
||||||
|
clientName: this.clientName,
|
||||||
|
accountNo: this.accountNo,
|
||||||
|
currentBalance: this.availableBalance,
|
||||||
|
transactionAmount: formAmount,
|
||||||
|
paymentTypeLabel
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ref.afterClosed().subscribe((result: any) => {
|
||||||
|
if (result?.confirm) {
|
||||||
|
this.postTransaction();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private postTransaction(): void {
|
||||||
const currentAccountTransactionFormData = this.currentAccountTransactionForm.value;
|
const currentAccountTransactionFormData = this.currentAccountTransactionForm.value;
|
||||||
const locale = this.settingsService.language.code;
|
const locale = this.settingsService.language.code;
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
@@ -160,9 +369,23 @@ export class CurrentAccountTransactionsComponent implements OnInit {
|
|||||||
locale
|
locale
|
||||||
};
|
};
|
||||||
data['transactionAmount'] = data['transactionAmount'] * 1;
|
data['transactionAmount'] = data['transactionAmount'] * 1;
|
||||||
|
if (this.denominationTotal > 0) {
|
||||||
|
const breakdown = [
|
||||||
|
...this.notes,
|
||||||
|
...this.coins
|
||||||
|
]
|
||||||
|
.map((d) => ({
|
||||||
|
denomination: d.value,
|
||||||
|
kind: d.kind,
|
||||||
|
count: this.countFor(d),
|
||||||
|
subtotal: this.subtotalFor(d)
|
||||||
|
}))
|
||||||
|
.filter((row) => row.count > 0);
|
||||||
|
data['denominationBreakdown'] = breakdown;
|
||||||
|
}
|
||||||
this.currentAccountsService
|
this.currentAccountsService
|
||||||
.executeCurrentAccountTransactionsCommand(this.currentAccountId, this.transactionCommand, data)
|
.executeCurrentAccountTransactionsCommand(this.currentAccountId, this.transactionCommand, data)
|
||||||
.subscribe((res) => {
|
.subscribe(() => {
|
||||||
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -215,6 +215,8 @@ export class FixedDepositAccountViewComponent implements OnInit {
|
|||||||
case 'Add Charge':
|
case 'Add Charge':
|
||||||
case 'Withdrawn by Client':
|
case 'Withdrawn by Client':
|
||||||
case 'Premature Close':
|
case 'Premature Close':
|
||||||
|
case 'Deposit':
|
||||||
|
case 'Withdrawal':
|
||||||
this.router.navigate([`actions/${name}`], { relativeTo: this.route });
|
this.router.navigate([`actions/${name}`], { relativeTo: this.route });
|
||||||
break;
|
break;
|
||||||
case 'Modify Application':
|
case 'Modify Application':
|
||||||
|
|||||||
+8
@@ -34,6 +34,14 @@ export class FixedDepositsButtonsConfiguration {
|
|||||||
switch (status) {
|
switch (status) {
|
||||||
case 'Active':
|
case 'Active':
|
||||||
this.buttonsArray = [
|
this.buttonsArray = [
|
||||||
|
{
|
||||||
|
name: 'Deposit',
|
||||||
|
icon: 'fa fa-arrow-down'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Withdrawal',
|
||||||
|
icon: 'fa fa-arrow-up'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Premature Close',
|
name: 'Premature Close',
|
||||||
icon: 'fa fa-arrow-left'
|
icon: 'fa fa-arrow-left'
|
||||||
|
|||||||
+1
-1
@@ -30,6 +30,6 @@
|
|||||||
@if (actions['Close']) {
|
@if (actions['Close']) {
|
||||||
<mifosx-close-fixed-deposits-account></mifosx-close-fixed-deposits-account>
|
<mifosx-close-fixed-deposits-account></mifosx-close-fixed-deposits-account>
|
||||||
}
|
}
|
||||||
@if (actions['Withdrawal']) {
|
@if (actions['Withdrawal'] || actions['Deposit']) {
|
||||||
<mifosx-fixed-deposits-cash-transaction></mifosx-fixed-deposits-cash-transaction>
|
<mifosx-fixed-deposits-cash-transaction></mifosx-fixed-deposits-cash-transaction>
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -55,6 +55,7 @@ export class FixedDepositsAccountActionsComponent {
|
|||||||
'Premature Close': boolean;
|
'Premature Close': boolean;
|
||||||
'Withdrawn by Client': boolean;
|
'Withdrawn by Client': boolean;
|
||||||
Withdrawal: boolean;
|
Withdrawal: boolean;
|
||||||
|
Deposit: boolean;
|
||||||
} = {
|
} = {
|
||||||
Approve: false,
|
Approve: false,
|
||||||
Reject: false,
|
Reject: false,
|
||||||
@@ -65,7 +66,8 @@ export class FixedDepositsAccountActionsComponent {
|
|||||||
'Add Charge': false,
|
'Add Charge': false,
|
||||||
'Premature Close': false,
|
'Premature Close': false,
|
||||||
'Withdrawn by Client': false,
|
'Withdrawn by Client': false,
|
||||||
Withdrawal: false
|
Withdrawal: false,
|
||||||
|
Deposit: false
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+281
-67
@@ -6,28 +6,240 @@
|
|||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="container mat-elevation-z8">
|
<div class="sat" [class.sat--deposit]="transactionType.deposit" [class.sat--withdrawal]="transactionType.withdrawal">
|
||||||
<mat-card>
|
<!-- Hero header -->
|
||||||
@if (transactionType.withdrawal) {
|
<div class="sat__hero">
|
||||||
<h2 mat-title>
|
<div class="sat__hero-top">
|
||||||
{{ 'labels.menus.' + actionName | translate }} {{ 'labels.heading.Fixed Deposit Transactions' | translate }}
|
<button type="button" mat-stroked-button class="sat__hero-back" [routerLink]="['../../general']">
|
||||||
</h2>
|
<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 Fixed Deposit' | translate)
|
||||||
|
: ('labels.heading.Deposit Money To Fixed 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>
|
||||||
|
|
||||||
<form [formGroup]="accountTransactionForm" (ngSubmit)="submit()">
|
<!-- Context strip -->
|
||||||
<mat-card-content class="layout-column">
|
<div class="sat__ctx">
|
||||||
<mat-form-field (click)="dueDatePicker.open()">
|
<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]="accountTransactionForm" (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>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="sat__denom-group">
|
||||||
|
<div class="sat__denom-group-head">
|
||||||
|
<span class="sat__denom-group-title">Banknotes</span>
|
||||||
|
<span class="sat__denom-group-total">₹{{ notesTotal | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-grid">
|
||||||
|
@for (d of notes; track d.value) {
|
||||||
|
<div class="sat__denom" [class.sat__denom--active]="countFor(d) > 0">
|
||||||
|
<div class="sat__denom-top">
|
||||||
|
<span class="sat__denom-chip sat__denom-chip--note">{{ d.label }}</span>
|
||||||
|
<span class="sat__denom-amount">₹{{ subtotalFor(d) | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-stepper">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="sat__denom-step"
|
||||||
|
(click)="bumpDenom(d, -1)"
|
||||||
|
[disabled]="countFor(d) === 0"
|
||||||
|
aria-label="Decrease"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
[value]="countFor(d)"
|
||||||
|
(input)="onDenomChange(d, $any($event.target).value)"
|
||||||
|
inputmode="numeric"
|
||||||
|
class="sat__denom-count"
|
||||||
|
/>
|
||||||
|
<button type="button" class="sat__denom-step" (click)="bumpDenom(d, 1)" aria-label="Increase">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sat__denom-group">
|
||||||
|
<div class="sat__denom-group-head">
|
||||||
|
<span class="sat__denom-group-title">Coins</span>
|
||||||
|
<span class="sat__denom-group-total">₹{{ coinsTotal | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-grid">
|
||||||
|
@for (d of coins; track d.value) {
|
||||||
|
<div class="sat__denom" [class.sat__denom--active]="countFor(d) > 0">
|
||||||
|
<div class="sat__denom-top">
|
||||||
|
<span class="sat__denom-chip sat__denom-chip--coin">{{ d.label }}</span>
|
||||||
|
<span class="sat__denom-amount">₹{{ subtotalFor(d) | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-stepper">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="sat__denom-step"
|
||||||
|
(click)="bumpDenom(d, -1)"
|
||||||
|
[disabled]="countFor(d) === 0"
|
||||||
|
aria-label="Decrease"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
[value]="countFor(d)"
|
||||||
|
(input)="onDenomChange(d, $any($event.target).value)"
|
||||||
|
inputmode="numeric"
|
||||||
|
class="sat__denom-count"
|
||||||
|
/>
|
||||||
|
<button type="button" class="sat__denom-step" (click)="bumpDenom(d, 1)" aria-label="Increase">+</button>
|
||||||
|
</div>
|
||||||
|
</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>
|
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
[min]="minDate"
|
[min]="minDate"
|
||||||
[max]="maxDate"
|
[max]="maxDate"
|
||||||
[matDatepicker]="dueDatePicker"
|
[matDatepicker]="transactionDatePicker"
|
||||||
formControlName="transactionDate"
|
|
||||||
required
|
required
|
||||||
|
formControlName="transactionDate"
|
||||||
/>
|
/>
|
||||||
<mat-datepicker-toggle matSuffix [for]="dueDatePicker"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #dueDatePicker></mat-datepicker>
|
<mat-datepicker #transactionDatePicker></mat-datepicker>
|
||||||
@if (accountTransactionForm.controls.transactionDate.hasError('required')) {
|
@if (accountTransactionForm.controls.transactionDate.hasError('required')) {
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
|
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
@@ -44,76 +256,78 @@
|
|||||||
>
|
>
|
||||||
</mifosx-input-amount>
|
</mifosx-input-amount>
|
||||||
|
|
||||||
<mat-form-field>
|
@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' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Payment Type' | translate }}</mat-label>
|
||||||
<mat-select required formControlName="paymentTypeId">
|
<mat-select formControlName="paymentTypeId">
|
||||||
@for (paymentType of paymentTypeOptions; track paymentType) {
|
@for (paymentType of paymentTypeOptions; track paymentType) {
|
||||||
<mat-option [value]="paymentType.id">
|
<mat-option [value]="paymentType.id">{{ paymentType.name }}</mat-option>
|
||||||
{{ paymentType.name }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
}
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="flex-fill">
|
<div class="sat__toggle-row" (click)="addPaymentDetails()">
|
||||||
<span class="expandcollapsebutton m-l-10 m-t-40 flex-75" (click)="addPaymentDetails()">
|
<mat-slide-toggle [checked]="!!addPaymentDetailsFlag">
|
||||||
<mat-slide-toggle>
|
<span class="sat__toggle-label">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
|
||||||
<div [className]="addPaymentDetailsFlag ? 'enabled' : 'disabled'">
|
</mat-slide-toggle>
|
||||||
<span class="m-l-10">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
|
|
||||||
</div>
|
|
||||||
</mat-slide-toggle>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
@if (addPaymentDetailsFlag) {
|
||||||
<mat-form-field>
|
<div class="sat__sub-grid">
|
||||||
<mat-label>{{ 'labels.inputs.Account Number' | translate }}</mat-label>
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<input type="number" formControlName="accountNumber" matInput />
|
<mat-label>{{ 'labels.inputs.Account Number' | translate }}</mat-label>
|
||||||
</mat-form-field>
|
<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>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
|
|
||||||
<input type="number" formControlName="checkNumber" matInput />
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
|
|
||||||
<input formControlName="routingCode" matInput />
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
|
|
||||||
<input formControlName="receiptNumber" matInput />
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (addPaymentDetailsFlag) {
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
|
|
||||||
<input formControlName="bankNumber" matInput />
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
|
|
||||||
<mat-form-field>
|
|
||||||
<mat-label>{{ 'labels.inputs.Notes' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Notes' | translate }}</mat-label>
|
||||||
<textarea formControlName="note" matInput cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
<textarea formControlName="note" matInput cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-card-actions class="layout-row layout-xs-column layout-align-center gap-5px">
|
<div class="sat__actions">
|
||||||
<button type="button" mat-raised-button [routerLink]="['../../transactions']">
|
<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 }}
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button mat-raised-button color="primary" [disabled]="!accountTransactionForm.valid">
|
<button mat-flat-button type="submit" class="sat__submit" [disabled]="!accountTransactionForm.valid">
|
||||||
|
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
</button>
|
</button>
|
||||||
</mat-card-actions>
|
</div>
|
||||||
</mat-card-content>
|
</div>
|
||||||
</form>
|
</section>
|
||||||
</mat-card>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+743
-7
@@ -6,18 +6,754 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.expandcollapsebutton {
|
$navy: #0d1b2a;
|
||||||
margin-top: -7px;
|
$slate: #475569;
|
||||||
|
$border: #e2e8f0;
|
||||||
|
$soft: #f8fafc;
|
||||||
|
$green: #059669;
|
||||||
|
$green-d: #047857;
|
||||||
|
$amber: #d97706;
|
||||||
|
$amber-d: #b45309;
|
||||||
|
|
||||||
|
.sat {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 16px 18px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.sat__hero {
|
||||||
max-width: 37rem;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 16px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 45%, #1e40af 100%);
|
||||||
|
box-shadow: 0 6px 18px -10px rgb(15 23 42 / 50%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at top right, rgb(255 255 255 / 12%), transparent 55%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-input {
|
.sat--withdrawal .sat__hero {
|
||||||
|
background: linear-gradient(135deg, #134e4a 0%, #115e59 45%, #0f766e 100%);
|
||||||
|
box-shadow: 0 6px 18px -10px rgb(15 118 110 / 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-back {
|
||||||
|
height: 30px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 0 11px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
background: rgb(255 255 255 / 14%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: 1px solid rgb(255 255 255 / 28%) !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgb(255 255 255 / 26%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-ico {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 28%);
|
||||||
|
color: #fff;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-title-wrap {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__sub {
|
||||||
|
margin: 1px 0 0;
|
||||||
|
font-size: 11.5px;
|
||||||
|
color: rgb(255 255 255 / 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-verify {
|
||||||
|
position: relative;
|
||||||
|
height: 30px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 0 12px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
color: #fff !important;
|
||||||
|
background: rgb(255 255 255 / 16%) !important;
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%) !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: visible !important;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -2px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid rgb(255 255 255 / 70%);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--pulse::before {
|
||||||
|
animation: sat-verify-pulse 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sat-verify-pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(1.18);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1.18);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%);
|
||||||
|
min-width: 130px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-label {
|
||||||
|
font-size: 9.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: rgb(255 255 255 / 78%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-sub {
|
||||||
|
font-size: 10px;
|
||||||
|
color: rgb(255 255 255 / 75%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgb(255 255 255 / 12%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 22%);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1 1 110px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-ico {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 25%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-label {
|
||||||
|
font-size: 9px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: rgb(255 255 255 / 70%);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-value {
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&--mono {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 10.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--amount {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--lg {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item--balance {
|
||||||
|
background: rgb(255 255 255 / 12%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 22%);
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-divider {
|
||||||
|
width: 1px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
margin: 4px 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (width <= 720px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 9px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%);
|
||||||
|
color: #fff;
|
||||||
|
align-self: flex-start;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
fa-icon {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--ok {
|
||||||
|
background: rgb(16 185 129 / 36%);
|
||||||
|
border-color: rgb(110 231 183 / 60%);
|
||||||
|
color: #d1fae5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warn {
|
||||||
|
background: rgb(245 158 11 / 36%);
|
||||||
|
border-color: rgb(252 211 77 / 60%);
|
||||||
|
color: #fef3c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--danger {
|
||||||
|
background: rgb(239 68 68 / 36%);
|
||||||
|
border-color: rgb(252 165 165 / 60%);
|
||||||
|
color: #fee2e2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
|
||||||
|
gap: 14px;
|
||||||
|
align-items: start;
|
||||||
|
|
||||||
|
@media (width <= 1100px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
background: $soft;
|
||||||
|
border-bottom: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-ico {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__panel-ico {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-action {
|
||||||
|
height: 26px !important;
|
||||||
|
border-radius: 7px !important;
|
||||||
|
font-size: 11.5px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
padding: 0 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-body {
|
||||||
|
padding: 12px 14px 14px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group {
|
||||||
|
padding: 8px 14px 4px;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-title {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: $slate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-total {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 6px 9px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s ease,
|
||||||
|
background 0.15s ease;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
border-color: #86efac;
|
||||||
|
background: #f0fdf4;
|
||||||
|
box-shadow: 0 1px 3px -1px rgb(4 120 87 / 18%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 48px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 9px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&--note {
|
||||||
|
background: linear-gradient(135deg, #ecfeff 0%, #cffafe 100%);
|
||||||
|
color: #155e75;
|
||||||
|
border: 1px solid #a5f3fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--coin {
|
||||||
|
background: linear-gradient(135deg, #fef9c3 0%, #fde68a 100%);
|
||||||
|
color: #854d0e;
|
||||||
|
border: 1px solid #fcd34d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-amount {
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-label {
|
.sat__denom-stepper {
|
||||||
padding-right: 25px !important;
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid $border;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-count {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
padding: 0 4px;
|
||||||
|
appearance: textfield;
|
||||||
|
|
||||||
|
&::-webkit-outer-spin-button,
|
||||||
|
&::-webkit-inner-spin-button {
|
||||||
|
appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-step {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 28px;
|
||||||
|
background: $soft;
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid $border;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $slate;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
border-left: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: $navy;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally {
|
||||||
|
margin: 8px 14px 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
border: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $slate;
|
||||||
|
|
||||||
|
&--grand {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px dashed #cbd5e1;
|
||||||
|
|
||||||
|
.sat__tally-label {
|
||||||
|
font-weight: 800;
|
||||||
|
color: $navy;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-value {
|
||||||
|
font-size: 15.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $green-d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__tally-row--grand .sat__tally-value {
|
||||||
|
color: $amber-d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-value {
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__apply {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
height: 34px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
box-shadow: 0 3px 10px -2px rgb(4 120 87 / 35%) !important;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
filter: grayscale(0.4);
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__apply {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__field {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-text-field-wrapper {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-form-field-infix {
|
||||||
|
min-height: 48px;
|
||||||
|
padding-top: 12px !important;
|
||||||
|
padding-bottom: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mdc-notched-outline__leading,
|
||||||
|
::ng-deep .mdc-notched-outline__notch,
|
||||||
|
::ng-deep .mdc-notched-outline__trailing {
|
||||||
|
border-color: #cbd5e1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__sub-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px 12px;
|
||||||
|
|
||||||
|
.sat__field--wide {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 600px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__toggle-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 4px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.sat__toggle-label {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $slate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__mismatch,
|
||||||
|
.sat__match {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
margin: 2px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__mismatch {
|
||||||
|
background: #fffbeb;
|
||||||
|
border: 1px solid #fde68a;
|
||||||
|
color: #92400e;
|
||||||
|
|
||||||
|
&-ico {
|
||||||
|
color: $amber;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__match {
|
||||||
|
background: #ecfdf5;
|
||||||
|
border: 1px solid #a7f3d0;
|
||||||
|
color: #065f46;
|
||||||
|
|
||||||
|
&-ico {
|
||||||
|
color: $green;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 42px !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
padding: 0 22px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__cancel {
|
||||||
|
color: $slate !important;
|
||||||
|
border-color: $border !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__submit {
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__submit {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
|
||||||
|
box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-r-6 {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 720px) {
|
||||||
|
.sat__hero-top {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item {
|
||||||
|
flex: 1 1 calc(50% - 14px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+175
-41
@@ -15,6 +15,7 @@ import {
|
|||||||
ReactiveFormsModule
|
ReactiveFormsModule
|
||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { FixedDepositsService } from '../../fixed-deposits.service';
|
import { FixedDepositsService } from '../../fixed-deposits.service';
|
||||||
|
import { ClientsService } from 'app/clients/clients.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
@@ -23,8 +24,18 @@ import { TransactionCommand, TransactionTypeFlags } from '../../../transaction.m
|
|||||||
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
|
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
|
||||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { TransactionConfirmDialogComponent } from '../../../../savings/saving-account-actions/savings-account-transactions/transaction-confirm-dialog/transaction-confirm-dialog.component';
|
||||||
|
import { IdentityVerifyDialogComponent } from '../../../../savings/saving-account-actions/savings-account-transactions/identity-verify-dialog/identity-verify-dialog.component';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
interface Denomination {
|
||||||
|
value: number;
|
||||||
|
kind: 'note' | 'coin';
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-fixed-deposits-cash-transaction',
|
selector: 'mifosx-fixed-deposits-cash-transaction',
|
||||||
templateUrl: './fixed-deposits-cash-transaction.component.html',
|
templateUrl: './fixed-deposits-cash-transaction.component.html',
|
||||||
@@ -33,7 +44,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
InputAmountComponent,
|
InputAmountComponent,
|
||||||
MatSlideToggle,
|
MatSlideToggle,
|
||||||
CdkTextareaAutosize
|
CdkTextareaAutosize,
|
||||||
|
FaIconComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class FixedDepositsCashTransactionComponent implements OnInit {
|
export class FixedDepositsCashTransactionComponent implements OnInit {
|
||||||
@@ -42,36 +54,47 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
private fixedDepositsService = inject(FixedDepositsService);
|
private fixedDepositsService = inject(FixedDepositsService);
|
||||||
|
private clientsService = inject(ClientsService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
|
||||||
/** Minimum Due Date allowed. */
|
|
||||||
minDate = new Date(2000, 0, 1);
|
minDate = new Date(2000, 0, 1);
|
||||||
/** Maximum Due Date allowed. */
|
|
||||||
maxDate = new Date();
|
maxDate = new Date();
|
||||||
/** Savings account transaction form. */
|
|
||||||
accountTransactionForm: UntypedFormGroup;
|
accountTransactionForm: UntypedFormGroup;
|
||||||
/** savings account transaction payment options. */
|
|
||||||
paymentTypeOptions: PaymentType[] = [];
|
paymentTypeOptions: PaymentType[] = [];
|
||||||
/** Flag to enable payment details fields. */
|
|
||||||
addPaymentDetailsFlag: Boolean = false;
|
addPaymentDetailsFlag: Boolean = false;
|
||||||
/** transaction type flag to render required UI */
|
|
||||||
transactionType: TransactionTypeFlags = { deposit: false, withdrawal: false };
|
transactionType: TransactionTypeFlags = { deposit: false, withdrawal: false };
|
||||||
/** transaction command for submit request */
|
|
||||||
transactionCommand: TransactionCommand;
|
transactionCommand: TransactionCommand;
|
||||||
actionName: string;
|
actionName: string;
|
||||||
/** saving account's Id */
|
|
||||||
accountId: string;
|
accountId: string;
|
||||||
currency: Currency;
|
currency: Currency;
|
||||||
|
|
||||||
/**
|
readonly notes: Denomination[] = [
|
||||||
* Retrieves the Saving Account transaction template data from `resolve`.
|
{ value: 500, kind: 'note', label: '₹500' },
|
||||||
* @param {FormBuilder} formBuilder Form Builder.
|
{ value: 200, kind: 'note', label: '₹200' },
|
||||||
* @param {FixedDepositsService} fixedDepositsService Fixed Deposit Service.
|
{ value: 100, kind: 'note', label: '₹100' },
|
||||||
* @param {ActivatedRoute} route Activated Route.
|
{ value: 50, kind: 'note', label: '₹50' },
|
||||||
* @param {Dates} dateUtils Date Utils.
|
{ value: 20, kind: 'note', label: '₹20' },
|
||||||
* @param {Router} router Router for navigation.
|
{ value: 10, kind: 'note', label: '₹10' }
|
||||||
* @param {SettingsService} settingsService Settings Service
|
];
|
||||||
*/
|
|
||||||
|
readonly coins: Denomination[] = [
|
||||||
|
{ value: 20, kind: 'coin', label: '₹20' },
|
||||||
|
{ value: 10, kind: 'coin', label: '₹10' },
|
||||||
|
{ value: 5, kind: 'coin', label: '₹5' },
|
||||||
|
{ value: 2, kind: 'coin', label: '₹2' },
|
||||||
|
{ value: 1, kind: 'coin', label: 'Re 1' }
|
||||||
|
];
|
||||||
|
|
||||||
|
denominationCounts: Record<string, number> = {};
|
||||||
|
|
||||||
|
accountNo: string | null = null;
|
||||||
|
clientName: string | null = null;
|
||||||
|
clientId: number | null = null;
|
||||||
|
kycStatus: 'Verified' | 'Partial' | 'Pending' = 'Pending';
|
||||||
|
availableBalance = 0;
|
||||||
|
identityVerified = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.route.data.subscribe((data: { fixedDepositsAccountActionData: any }) => {
|
this.route.data.subscribe((data: { fixedDepositsAccountActionData: any }) => {
|
||||||
this.currency = data.fixedDepositsAccountActionData.currency;
|
this.currency = data.fixedDepositsAccountActionData.currency;
|
||||||
@@ -85,21 +108,43 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error(`Invalid transaction action: ${this.actionName}`);
|
throw new Error(`Invalid transaction action: ${this.actionName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.accountId = this.route.parent.snapshot.params['fixedDepositAccountId'];
|
this.accountId = this.route.parent.snapshot.params['fixedDepositAccountId'];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the Saving account transaction form when component loads.
|
|
||||||
*/
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.maxDate = this.settingsService.businessDate;
|
this.maxDate = this.settingsService.businessDate;
|
||||||
this.createSavingAccountTransactionForm();
|
this.createSavingAccountTransactionForm();
|
||||||
|
this.loadContextStrip();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadContextStrip(): void {
|
||||||
|
if (!this.accountId) return;
|
||||||
|
this.fixedDepositsService.getFixedDepositsAccountData(this.accountId).subscribe({
|
||||||
|
next: (account: any) => {
|
||||||
|
this.accountNo = account?.accountNo ?? null;
|
||||||
|
this.clientName = account?.clientName ?? account?.groupName ?? null;
|
||||||
|
this.clientId = account?.clientId ?? null;
|
||||||
|
if (!this.currency && account?.currency) this.currency = account.currency;
|
||||||
|
this.availableBalance = Number(account?.summary?.accountBalance ?? account?.summary?.availableBalance ?? 0);
|
||||||
|
if (account?.clientId) {
|
||||||
|
this.fetchClientKyc(String(account.clientId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchClientKyc(clientId: string): void {
|
||||||
|
this.clientsService.getClientKyc(clientId).subscribe({
|
||||||
|
next: (kyc: any) => {
|
||||||
|
const pan = !!kyc?.panVerified;
|
||||||
|
const aadhaar = !!kyc?.aadhaarVerified;
|
||||||
|
this.kycStatus = pan && aadhaar ? 'Verified' : pan || aadhaar ? 'Partial' : 'Pending';
|
||||||
|
},
|
||||||
|
error: () => {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to create the Saving Account Transaction Form.
|
|
||||||
*/
|
|
||||||
createSavingAccountTransactionForm() {
|
createSavingAccountTransactionForm() {
|
||||||
this.accountTransactionForm = this.formBuilder.group({
|
this.accountTransactionForm = this.formBuilder.group({
|
||||||
transactionDate: [
|
transactionDate: [
|
||||||
@@ -115,9 +160,71 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ── Denomination helpers ────────────────────────────────────────────
|
||||||
* Method to add payment detail fields to the UI.
|
denomKey(d: Denomination): string {
|
||||||
*/
|
return `${d.kind}:${d.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
countFor(d: Denomination): number {
|
||||||
|
return this.denominationCounts[this.denomKey(d)] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subtotalFor(d: Denomination): number {
|
||||||
|
return this.countFor(d) * d.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDenomChange(d: Denomination, raw: any): void {
|
||||||
|
const n = Math.max(0, Math.floor(Number(raw) || 0));
|
||||||
|
this.denominationCounts[this.denomKey(d)] = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
bumpDenom(d: Denomination, delta: number): void {
|
||||||
|
const next = Math.max(0, this.countFor(d) + delta);
|
||||||
|
this.denominationCounts[this.denomKey(d)] = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
get notesTotal(): number {
|
||||||
|
return this.notes.reduce((s, d) => s + this.subtotalFor(d), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get coinsTotal(): number {
|
||||||
|
return this.coins.reduce((s, d) => s + this.subtotalFor(d), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get denominationTotal(): number {
|
||||||
|
return this.notesTotal + this.coinsTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalPieces(): number {
|
||||||
|
return Object.values(this.denominationCounts).reduce((s, n) => s + (n || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get denominationMatchesForm(): boolean {
|
||||||
|
const formAmt = Number(this.accountTransactionForm?.controls?.['transactionAmount']?.value || 0);
|
||||||
|
return this.denominationTotal > 0 && formAmt === this.denominationTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyDenominationTotal(): void {
|
||||||
|
this.accountTransactionForm.controls['transactionAmount'].setValue(this.denominationTotal);
|
||||||
|
this.accountTransactionForm.controls['transactionAmount'].markAsDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearDenominations(): void {
|
||||||
|
this.denominationCounts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
openIdentityVerify(): void {
|
||||||
|
if (!this.clientId) return;
|
||||||
|
this.identityVerified = true;
|
||||||
|
this.dialog.open(IdentityVerifyDialogComponent, {
|
||||||
|
width: '500px',
|
||||||
|
maxWidth: '92vw',
|
||||||
|
panelClass: 'idv-panel',
|
||||||
|
autoFocus: false,
|
||||||
|
data: { clientId: this.clientId, clientName: this.clientName, accountNo: this.accountNo }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addPaymentDetails() {
|
addPaymentDetails() {
|
||||||
this.addPaymentDetailsFlag = !this.addPaymentDetailsFlag;
|
this.addPaymentDetailsFlag = !this.addPaymentDetailsFlag;
|
||||||
if (this.addPaymentDetailsFlag) {
|
if (this.addPaymentDetailsFlag) {
|
||||||
@@ -135,27 +242,54 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method to submit the transaction details.
|
|
||||||
*/
|
|
||||||
submit() {
|
submit() {
|
||||||
const savingAccountTransactionFormData = this.accountTransactionForm.value;
|
const formAmount = Number(this.accountTransactionForm.value.transactionAmount) || 0;
|
||||||
|
const paymentTypeId = this.accountTransactionForm.value.paymentTypeId;
|
||||||
|
const paymentTypeLabel = this.paymentTypeOptions?.find((p: any) => p.id === paymentTypeId)?.name ?? null;
|
||||||
|
|
||||||
|
const ref = this.dialog.open(TransactionConfirmDialogComponent, {
|
||||||
|
width: '480px',
|
||||||
|
maxWidth: '92vw',
|
||||||
|
panelClass: 'tcd-panel',
|
||||||
|
autoFocus: false,
|
||||||
|
data: {
|
||||||
|
kind: this.transactionType.deposit ? 'deposit' : 'withdrawal',
|
||||||
|
clientName: this.clientName,
|
||||||
|
accountNo: this.accountNo,
|
||||||
|
currentBalance: this.availableBalance,
|
||||||
|
transactionAmount: formAmount,
|
||||||
|
paymentTypeLabel
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ref.afterClosed().subscribe((result: any) => {
|
||||||
|
if (result?.confirm) {
|
||||||
|
this.postTransaction();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private postTransaction(): void {
|
||||||
|
const formData = this.accountTransactionForm.value;
|
||||||
const locale = this.settingsService.language.code;
|
const locale = this.settingsService.language.code;
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
const prevTransactionDate: Date = this.accountTransactionForm.value.transactionDate;
|
const prevDate: Date = this.accountTransactionForm.value.transactionDate;
|
||||||
if (savingAccountTransactionFormData.transactionDate instanceof Date) {
|
if (formData.transactionDate instanceof Date) {
|
||||||
savingAccountTransactionFormData.transactionDate = this.dateUtils.formatDate(prevTransactionDate, dateFormat);
|
formData.transactionDate = this.dateUtils.formatDate(prevDate, dateFormat);
|
||||||
}
|
}
|
||||||
const data = {
|
const data = { ...formData, dateFormat, locale };
|
||||||
...savingAccountTransactionFormData,
|
|
||||||
dateFormat,
|
|
||||||
locale
|
|
||||||
};
|
|
||||||
delete data.note;
|
delete data.note;
|
||||||
data['transactionAmount'] = data['transactionAmount'] * 1;
|
data['transactionAmount'] = data['transactionAmount'] * 1;
|
||||||
|
if (this.denominationTotal > 0) {
|
||||||
|
data['denominationBreakdown'] = [
|
||||||
|
...this.notes,
|
||||||
|
...this.coins
|
||||||
|
]
|
||||||
|
.map((d) => ({ denomination: d.value, kind: d.kind, count: this.countFor(d), subtotal: this.subtotalFor(d) }))
|
||||||
|
.filter((row) => row.count > 0);
|
||||||
|
}
|
||||||
this.fixedDepositsService
|
this.fixedDepositsService
|
||||||
.executeFixedDepositsAccountTransactionsCommand(this.accountId, this.transactionCommand, data)
|
.executeFixedDepositsAccountTransactionsCommand(this.accountId, this.transactionCommand, data)
|
||||||
.subscribe((res) => {
|
.subscribe(() => {
|
||||||
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
this.router.navigate(['../../transactions'], { relativeTo: this.route });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+310
-86
@@ -6,110 +6,334 @@
|
|||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="container">
|
<div class="sat" [class.sat--deposit]="transactionType.deposit" [class.sat--withdrawal]="transactionType.withdrawal">
|
||||||
<mat-card>
|
<!-- Hero header -->
|
||||||
@if (transactionType.withdrawal) {
|
<div class="sat__hero">
|
||||||
<h2 mat-title>
|
<div class="sat__hero-top">
|
||||||
{{ 'labels.menus.' + actionName | translate }} {{ 'labels.heading.Recurring Deposit Transactions' | translate }}
|
<button type="button" mat-stroked-button class="sat__hero-back" [routerLink]="['../../general']">
|
||||||
</h2>
|
<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>
|
||||||
|
|
||||||
<form [formGroup]="depositRecurringDepositForm" (ngSubmit)="submit()">
|
<!-- Context strip -->
|
||||||
<mat-card-content>
|
<div class="sat__ctx">
|
||||||
<div class="layout-column">
|
<div class="sat__ctx-item">
|
||||||
<mat-form-field (click)="transactionDatePicker.open()">
|
<fa-icon icon="user" class="sat__ctx-ico"></fa-icon>
|
||||||
<mat-label>{{ 'labels.inputs.Transaction Date' | translate }}</mat-label>
|
<div class="sat__ctx-text">
|
||||||
<input
|
<span class="sat__ctx-label">Client</span>
|
||||||
matInput
|
<span class="sat__ctx-value">{{ clientName || '—' }}</span>
|
||||||
[min]="minDate"
|
</div>
|
||||||
[max]="maxDate"
|
</div>
|
||||||
[matDatepicker]="transactionDatePicker"
|
<div class="sat__ctx-divider"></div>
|
||||||
required
|
<div class="sat__ctx-item">
|
||||||
formControlName="transactionDate"
|
<fa-icon icon="hashtag" class="sat__ctx-ico"></fa-icon>
|
||||||
/>
|
<div class="sat__ctx-text">
|
||||||
<mat-datepicker-toggle matSuffix [for]="transactionDatePicker"></mat-datepicker-toggle>
|
<span class="sat__ctx-label">Account No</span>
|
||||||
<mat-datepicker #transactionDatePicker></mat-datepicker>
|
<span class="sat__ctx-value sat__ctx-value--mono">{{ accountNo || '—' }}</span>
|
||||||
@if (depositRecurringDepositForm.controls.transactionDate.hasError('required')) {
|
</div>
|
||||||
<mat-error>
|
</div>
|
||||||
{{ 'labels.inputs.Transaction Date' | translate }} {{ 'labels.commons.is' | translate }}
|
<div class="sat__ctx-divider"></div>
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
<div class="sat__ctx-item sat__ctx-item--balance">
|
||||||
</mat-error>
|
<fa-icon icon="wallet" class="sat__ctx-ico"></fa-icon>
|
||||||
}
|
<div class="sat__ctx-text">
|
||||||
</mat-form-field>
|
<span class="sat__ctx-label">Current Balance</span>
|
||||||
|
<span class="sat__ctx-value sat__ctx-value--amount sat__ctx-value--lg">
|
||||||
<mifosx-input-amount
|
₹{{ availableBalance | number: '1.2-2' }}
|
||||||
[currency]="currency"
|
</span>
|
||||||
[isRequired]="true"
|
</div>
|
||||||
[inputFormControl]="depositRecurringDepositForm.controls.transactionAmount"
|
</div>
|
||||||
[inputLabel]="'Transaction Amount'"
|
<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'"
|
||||||
>
|
>
|
||||||
</mifosx-input-amount>
|
<fa-icon
|
||||||
|
[icon]="
|
||||||
|
kycStatus === 'Verified'
|
||||||
|
? 'check-circle'
|
||||||
|
: kycStatus === 'Partial'
|
||||||
|
? 'exclamation-triangle'
|
||||||
|
: 'times-circle'
|
||||||
|
"
|
||||||
|
></fa-icon>
|
||||||
|
{{ kycStatus }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-form-field>
|
<form [formGroup]="depositRecurringDepositForm" (ngSubmit)="submit()" class="sat__form">
|
||||||
<mat-label>{{ 'labels.inputs.Payment Type Id' | translate }}</mat-label>
|
<!-- LEFT: denomination breakdown -->
|
||||||
<mat-select formControlName="paymentTypeId" required>
|
<section class="sat__panel sat__panel--denom">
|
||||||
@for (paymentType of paymentTypes; track paymentType) {
|
<header class="sat__panel-head">
|
||||||
<mat-option [value]="paymentType.id">
|
<fa-icon icon="money-bill-wave" class="sat__panel-ico"></fa-icon>
|
||||||
{{ paymentType.name }}
|
<h3 class="sat__panel-title">Cash Denomination Breakdown</h3>
|
||||||
</mat-option>
|
<span class="sat__panel-spacer"></span>
|
||||||
}
|
<button type="button" mat-stroked-button class="sat__panel-action" (click)="clearDenominations()">
|
||||||
</mat-select>
|
<fa-icon icon="undo" class="m-r-6"></fa-icon> Clear
|
||||||
@if (depositRecurringDepositForm.controls.paymentTypeId.hasError('required')) {
|
</button>
|
||||||
<mat-error>
|
</header>
|
||||||
{{ 'labels.inputs.Payment Type' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<div class="flex-fill">
|
<div class="sat__denom-group">
|
||||||
<span class="expandcollapsebutton m-l-10 m-t-40 flex-75" (click)="addPaymentDetails()">
|
<div class="sat__denom-group-head">
|
||||||
<mat-slide-toggle>
|
<span class="sat__denom-group-title">Banknotes</span>
|
||||||
<div [className]="showPaymentDetails ? 'enabled' : 'disabled'">
|
<span class="sat__denom-group-total">₹{{ notesTotal | number: '1.2-2' }}</span>
|
||||||
<span class="m-l-10">{{ 'labels.inputs.Show Payment Details' | translate }}</span>
|
</div>
|
||||||
</div>
|
<div class="sat__denom-grid">
|
||||||
</mat-slide-toggle>
|
@for (d of notes; track d.value) {
|
||||||
|
<div class="sat__denom" [class.sat__denom--active]="countFor(d) > 0">
|
||||||
|
<div class="sat__denom-top">
|
||||||
|
<span class="sat__denom-chip sat__denom-chip--note">{{ d.label }}</span>
|
||||||
|
<span class="sat__denom-amount">₹{{ subtotalFor(d) | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-stepper">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="sat__denom-step"
|
||||||
|
(click)="bumpDenom(d, -1)"
|
||||||
|
[disabled]="countFor(d) === 0"
|
||||||
|
aria-label="Decrease"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
[value]="countFor(d)"
|
||||||
|
(input)="onDenomChange(d, $any($event.target).value)"
|
||||||
|
inputmode="numeric"
|
||||||
|
class="sat__denom-count"
|
||||||
|
/>
|
||||||
|
<button type="button" class="sat__denom-step" (click)="bumpDenom(d, 1)" aria-label="Increase">+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sat__denom-group">
|
||||||
|
<div class="sat__denom-group-head">
|
||||||
|
<span class="sat__denom-group-title">Coins</span>
|
||||||
|
<span class="sat__denom-group-total">₹{{ coinsTotal | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-grid">
|
||||||
|
@for (d of coins; track d.value) {
|
||||||
|
<div class="sat__denom" [class.sat__denom--active]="countFor(d) > 0">
|
||||||
|
<div class="sat__denom-top">
|
||||||
|
<span class="sat__denom-chip sat__denom-chip--coin">{{ d.label }}</span>
|
||||||
|
<span class="sat__denom-amount">₹{{ subtotalFor(d) | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="sat__denom-stepper">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="sat__denom-step"
|
||||||
|
(click)="bumpDenom(d, -1)"
|
||||||
|
[disabled]="countFor(d) === 0"
|
||||||
|
aria-label="Decrease"
|
||||||
|
>
|
||||||
|
−
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
[value]="countFor(d)"
|
||||||
|
(input)="onDenomChange(d, $any($event.target).value)"
|
||||||
|
inputmode="numeric"
|
||||||
|
class="sat__denom-count"
|
||||||
|
/>
|
||||||
|
<button type="button" class="sat__denom-step" (click)="bumpDenom(d, 1)" aria-label="Increase">+</button>
|
||||||
|
</div>
|
||||||
|
</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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
@if (denominationMatchesForm) {
|
||||||
|
<div class="sat__match">
|
||||||
|
<fa-icon icon="check-circle" class="sat__match-ico"></fa-icon>
|
||||||
|
Cash count matches transaction amount.
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (showPaymentDetails) {
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<mat-form-field>
|
<mat-label>{{ 'labels.inputs.Payment Type Id' | translate }}</mat-label>
|
||||||
<mat-label> {{ 'labels.inputs.Account' | 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" />
|
<input matInput formControlName="accountNumber" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<mat-label>{{ 'labels.inputs.Cheque' | translate }} #</mat-label>
|
<mat-label>{{ 'labels.inputs.Cheque' | translate }}</mat-label>
|
||||||
<input matInput formControlName="checkNumber" />
|
<input matInput formControlName="checkNumber" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Routing Code' | translate }}</mat-label>
|
||||||
<input matInput formControlName="routingCode" />
|
<input matInput formControlName="routingCode" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<mat-label>{{ 'labels.inputs.Reciept' | translate }} #</mat-label>
|
<mat-label>{{ 'labels.inputs.Receipt Number' | translate }}</mat-label>
|
||||||
<input matInput formControlName="receiptNumber" />
|
<input matInput formControlName="receiptNumber" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline" class="sat__field sat__field--wide">
|
||||||
<mat-label>{{ 'labels.inputs.Bank' | translate }} #</mat-label>
|
<mat-label>{{ 'labels.inputs.Bank' | translate }}</mat-label>
|
||||||
<input matInput formControlName="bankNumber" />
|
<input matInput formControlName="bankNumber" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field>
|
<mat-form-field appearance="outline" class="sat__field">
|
||||||
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Note' | translate }}</mat-label>
|
||||||
<textarea matInput formControlName="note" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
<textarea formControlName="note" matInput cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||||||
</mat-form-field>
|
</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">
|
||||||
|
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||||
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-card-content>
|
</div>
|
||||||
|
</section>
|
||||||
<mat-card-actions class="layout-row align-center gap-5px responsive-column">
|
</form>
|
||||||
<button type="button" mat-raised-button [routerLink]="['../../general']">
|
|
||||||
{{ 'labels.buttons.Cancel' | translate }}
|
|
||||||
</button>
|
|
||||||
<button mat-raised-button color="primary" [disabled]="!depositRecurringDepositForm.valid">
|
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
|
||||||
</button>
|
|
||||||
</mat-card-actions>
|
|
||||||
</form>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+743
-8
@@ -6,19 +6,754 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.container {
|
$navy: #0d1b2a;
|
||||||
max-width: 37rem;
|
$slate: #475569;
|
||||||
|
$border: #e2e8f0;
|
||||||
|
$soft: #f8fafc;
|
||||||
|
$green: #059669;
|
||||||
|
$green-d: #047857;
|
||||||
|
$amber: #d97706;
|
||||||
|
$amber-d: #b45309;
|
||||||
|
|
||||||
|
.sat {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 16px 18px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expandcollapsebutton {
|
.sat__hero {
|
||||||
margin-top: -7px;
|
display: flex;
|
||||||
margin-left: 2%;
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 16px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #fff;
|
||||||
|
background: linear-gradient(135deg, #0f172a 0%, #1e3a8a 45%, #1e40af 100%);
|
||||||
|
box-shadow: 0 6px 18px -10px rgb(15 23 42 / 50%);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at top right, rgb(255 255 255 / 12%), transparent 55%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-input {
|
.sat--withdrawal .sat__hero {
|
||||||
|
background: linear-gradient(135deg, #134e4a 0%, #115e59 45%, #0f766e 100%);
|
||||||
|
box-shadow: 0 6px 18px -10px rgb(15 118 110 / 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-back {
|
||||||
|
height: 30px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 0 11px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
background: rgb(255 255 255 / 14%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
border: 1px solid rgb(255 255 255 / 28%) !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgb(255 255 255 / 26%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-ico {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 28%);
|
||||||
|
color: #fff;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-title-wrap {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__sub {
|
||||||
|
margin: 1px 0 0;
|
||||||
|
font-size: 11.5px;
|
||||||
|
color: rgb(255 255 255 / 80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-verify {
|
||||||
|
position: relative;
|
||||||
|
height: 30px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 0 12px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
color: #fff !important;
|
||||||
|
background: rgb(255 255 255 / 16%) !important;
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%) !important;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: visible !important;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -2px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 2px solid rgb(255 255 255 / 70%);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
will-change: transform, opacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--pulse::before {
|
||||||
|
animation: sat-verify-pulse 1.6s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sat-verify-pulse {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
transform: scale(1.18);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1.18);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 5px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%);
|
||||||
|
min-width: 130px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-label {
|
||||||
|
font-size: 9.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: rgb(255 255 255 / 78%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-value {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #fff;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta-sub {
|
||||||
|
font-size: 10px;
|
||||||
|
color: rgb(255 255 255 / 75%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 5px 8px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: rgb(255 255 255 / 12%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 22%);
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1 1 110px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-ico {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 25%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 10px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-label {
|
||||||
|
font-size: 9px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: rgb(255 255 255 / 70%);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-value {
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&--mono {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 10.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--amount {
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--lg {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item--balance {
|
||||||
|
background: rgb(255 255 255 / 12%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 22%);
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 3px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-divider {
|
||||||
|
width: 1px;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
margin: 4px 0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
@media (width <= 720px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 9px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: rgb(255 255 255 / 18%);
|
||||||
|
border: 1px solid rgb(255 255 255 / 30%);
|
||||||
|
color: #fff;
|
||||||
|
align-self: flex-start;
|
||||||
|
width: fit-content;
|
||||||
|
|
||||||
|
fa-icon {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--ok {
|
||||||
|
background: rgb(16 185 129 / 36%);
|
||||||
|
border-color: rgb(110 231 183 / 60%);
|
||||||
|
color: #d1fae5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warn {
|
||||||
|
background: rgb(245 158 11 / 36%);
|
||||||
|
border-color: rgb(252 211 77 / 60%);
|
||||||
|
color: #fef3c7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--danger {
|
||||||
|
background: rgb(239 68 68 / 36%);
|
||||||
|
border-color: rgb(252 165 165 / 60%);
|
||||||
|
color: #fee2e2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
|
||||||
|
gap: 14px;
|
||||||
|
align-items: start;
|
||||||
|
|
||||||
|
@media (width <= 1100px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 1px 3px rgb(15 23 42 / 4%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
background: $soft;
|
||||||
|
border-bottom: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-ico {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 7px;
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__panel-ico {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-action {
|
||||||
|
height: 26px !important;
|
||||||
|
border-radius: 7px !important;
|
||||||
|
font-size: 11.5px !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
padding: 0 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__panel-body {
|
||||||
|
padding: 12px 14px 14px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group {
|
||||||
|
padding: 8px 14px 4px;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: baseline;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-title {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: $slate;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-group-total {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 6px 9px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid $border;
|
||||||
|
transition:
|
||||||
|
border-color 0.15s ease,
|
||||||
|
background 0.15s ease;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
border-color: #86efac;
|
||||||
|
background: #f0fdf4;
|
||||||
|
box-shadow: 0 1px 3px -1px rgb(4 120 87 / 18%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 6px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 48px;
|
||||||
|
height: 22px;
|
||||||
|
padding: 0 9px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&--note {
|
||||||
|
background: linear-gradient(135deg, #ecfeff 0%, #cffafe 100%);
|
||||||
|
color: #155e75;
|
||||||
|
border: 1px solid #a5f3fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--coin {
|
||||||
|
background: linear-gradient(135deg, #fef9c3 0%, #fde68a 100%);
|
||||||
|
color: #854d0e;
|
||||||
|
border: 1px solid #fcd34d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-amount {
|
||||||
|
font-size: 11.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.right-label {
|
.sat__denom-stepper {
|
||||||
padding-right: 25px !important;
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid $border;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-count {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
background: transparent;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12.5px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
padding: 0 4px;
|
||||||
|
appearance: textfield;
|
||||||
|
|
||||||
|
&::-webkit-outer-spin-button,
|
||||||
|
&::-webkit-inner-spin-button {
|
||||||
|
appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__denom-step {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 28px;
|
||||||
|
background: $soft;
|
||||||
|
border: none;
|
||||||
|
border-right: 1px solid $border;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $slate;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
border-left: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: #e2e8f0;
|
||||||
|
color: $navy;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.35;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally {
|
||||||
|
margin: 8px 14px 14px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
border: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3px 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: $slate;
|
||||||
|
|
||||||
|
&--grand {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px dashed #cbd5e1;
|
||||||
|
|
||||||
|
.sat__tally-label {
|
||||||
|
font-weight: 800;
|
||||||
|
color: $navy;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-value {
|
||||||
|
font-size: 15.5px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $green-d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__tally-row--grand .sat__tally-value {
|
||||||
|
color: $amber-d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__tally-value {
|
||||||
|
font-weight: 700;
|
||||||
|
color: $navy;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__apply {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 8px;
|
||||||
|
height: 34px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
font-size: 12.5px !important;
|
||||||
|
box-shadow: 0 3px 10px -2px rgb(4 120 87 / 35%) !important;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
filter: grayscale(0.4);
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__apply {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__field {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-text-field-wrapper {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mat-mdc-form-field-infix {
|
||||||
|
min-height: 48px;
|
||||||
|
padding-top: 12px !important;
|
||||||
|
padding-bottom: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::ng-deep .mdc-notched-outline__leading,
|
||||||
|
::ng-deep .mdc-notched-outline__notch,
|
||||||
|
::ng-deep .mdc-notched-outline__trailing {
|
||||||
|
border-color: #cbd5e1 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__sub-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 8px 12px;
|
||||||
|
|
||||||
|
.sat__field--wide {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 600px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__toggle-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 4px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.sat__toggle-label {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $slate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__mismatch,
|
||||||
|
.sat__match {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 12.5px;
|
||||||
|
margin: 2px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__mismatch {
|
||||||
|
background: #fffbeb;
|
||||||
|
border: 1px solid #fde68a;
|
||||||
|
color: #92400e;
|
||||||
|
|
||||||
|
&-ico {
|
||||||
|
color: $amber;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__match {
|
||||||
|
background: #ecfdf5;
|
||||||
|
border: 1px solid #a7f3d0;
|
||||||
|
color: #065f46;
|
||||||
|
|
||||||
|
&-ico {
|
||||||
|
color: $green;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
|
||||||
|
button {
|
||||||
|
height: 42px !important;
|
||||||
|
border-radius: 10px !important;
|
||||||
|
font-weight: 700 !important;
|
||||||
|
padding: 0 22px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__cancel {
|
||||||
|
color: $slate !important;
|
||||||
|
border-color: $border !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__submit {
|
||||||
|
background: linear-gradient(135deg, $green 0%, $green-d 100%) !important;
|
||||||
|
color: #fff !important;
|
||||||
|
box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.55;
|
||||||
|
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%) !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat--withdrawal .sat__submit {
|
||||||
|
background: linear-gradient(135deg, $amber 0%, $amber-d 100%) !important;
|
||||||
|
box-shadow: 0 4px 12px -2px rgb(180 83 9 / 35%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-r-6 {
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 720px) {
|
||||||
|
.sat__hero-top {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__hero-meta {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sat__ctx-item {
|
||||||
|
flex: 1 1 calc(50% - 14px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+173
-43
@@ -19,6 +19,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { RecurringDepositsService } from '../../recurring-deposits.service';
|
import { RecurringDepositsService } from '../../recurring-deposits.service';
|
||||||
|
import { ClientsService } from 'app/clients/clients.service';
|
||||||
import { SettingsService } from 'app/settings/settings.service';
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { Dates } from 'app/core/utils/dates';
|
import { Dates } from 'app/core/utils/dates';
|
||||||
import { Currency } from 'app/shared/models/general.model';
|
import { Currency } from 'app/shared/models/general.model';
|
||||||
@@ -26,8 +27,18 @@ import { TransactionCommand, TransactionTypeFlags } from '../../../transaction.m
|
|||||||
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
|
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
|
||||||
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
import { MatSlideToggle } from '@angular/material/slide-toggle';
|
||||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { TransactionConfirmDialogComponent } from '../../../../savings/saving-account-actions/savings-account-transactions/transaction-confirm-dialog/transaction-confirm-dialog.component';
|
||||||
|
import { IdentityVerifyDialogComponent } from '../../../../savings/saving-account-actions/savings-account-transactions/identity-verify-dialog/identity-verify-dialog.component';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
interface Denomination {
|
||||||
|
value: number;
|
||||||
|
kind: 'note' | 'coin';
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deposits Recurring Deposits Account Component
|
* Deposits Recurring Deposits Account Component
|
||||||
*/
|
*/
|
||||||
@@ -39,7 +50,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
InputAmountComponent,
|
InputAmountComponent,
|
||||||
MatSlideToggle,
|
MatSlideToggle,
|
||||||
CdkTextareaAutosize
|
CdkTextareaAutosize,
|
||||||
|
FaIconComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class DepositRecurringDepositsAccountComponent implements OnInit {
|
export class DepositRecurringDepositsAccountComponent implements OnInit {
|
||||||
@@ -48,25 +60,19 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private dateUtils = inject(Dates);
|
private dateUtils = inject(Dates);
|
||||||
private recurringDepositsService = inject(RecurringDepositsService);
|
private recurringDepositsService = inject(RecurringDepositsService);
|
||||||
|
private clientsService = inject(ClientsService);
|
||||||
private settingsService = inject(SettingsService);
|
private settingsService = inject(SettingsService);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
|
||||||
@Input() currency: Currency;
|
@Input() currency: Currency;
|
||||||
|
|
||||||
/** Transactions Amount */
|
|
||||||
transactionAmount: any;
|
transactionAmount: any;
|
||||||
/** Outstanding Charge Amount */
|
|
||||||
outstandingChargeAmount: any;
|
outstandingChargeAmount: any;
|
||||||
/** Payment Types */
|
|
||||||
paymentTypes: any;
|
paymentTypes: any;
|
||||||
/** Account Id */
|
|
||||||
accountId: string;
|
accountId: string;
|
||||||
/** Show payment details */
|
|
||||||
showPaymentDetails = false;
|
showPaymentDetails = false;
|
||||||
/** Minimum Date allowed. */
|
|
||||||
minDate = new Date(2000, 0, 1);
|
minDate = new Date(2000, 0, 1);
|
||||||
/** Maximum Date allowed. */
|
|
||||||
maxDate = new Date();
|
maxDate = new Date();
|
||||||
/** Deposits Recurring Deposit Account form. */
|
|
||||||
depositRecurringDepositForm: UntypedFormGroup;
|
depositRecurringDepositForm: UntypedFormGroup;
|
||||||
|
|
||||||
action: string;
|
action: string;
|
||||||
@@ -74,14 +80,32 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
|||||||
transactionCommand: TransactionCommand;
|
transactionCommand: TransactionCommand;
|
||||||
transactionType: TransactionTypeFlags = { deposit: false, withdrawal: false };
|
transactionType: TransactionTypeFlags = { deposit: false, withdrawal: false };
|
||||||
|
|
||||||
/**
|
readonly notes: Denomination[] = [
|
||||||
* Retrieves action details transactions template data from `resolve`
|
{ value: 500, kind: 'note', label: '₹500' },
|
||||||
* @param {FormBuilder} formBuilder Form Builder
|
{ value: 200, kind: 'note', label: '₹200' },
|
||||||
* @param {ActivatedRoute} route Activated Route
|
{ value: 100, kind: 'note', label: '₹100' },
|
||||||
* @param {Router} router Router
|
{ value: 50, kind: 'note', label: '₹50' },
|
||||||
* @param {Dates} dateUtils Date Utils
|
{ value: 20, kind: 'note', label: '₹20' },
|
||||||
* @param {SettingsService} settingsService Settings Service
|
{ value: 10, kind: 'note', label: '₹10' }
|
||||||
*/
|
];
|
||||||
|
|
||||||
|
readonly coins: Denomination[] = [
|
||||||
|
{ value: 20, kind: 'coin', label: '₹20' },
|
||||||
|
{ value: 10, kind: 'coin', label: '₹10' },
|
||||||
|
{ value: 5, kind: 'coin', label: '₹5' },
|
||||||
|
{ value: 2, kind: 'coin', label: '₹2' },
|
||||||
|
{ value: 1, kind: 'coin', label: 'Re 1' }
|
||||||
|
];
|
||||||
|
|
||||||
|
denominationCounts: Record<string, number> = {};
|
||||||
|
|
||||||
|
accountNo: string | null = null;
|
||||||
|
clientName: string | null = null;
|
||||||
|
clientId: number | null = null;
|
||||||
|
kycStatus: 'Verified' | 'Partial' | 'Pending' = 'Pending';
|
||||||
|
availableBalance = 0;
|
||||||
|
identityVerified = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.route.data.subscribe((data: { recurringDepositsAccountActionData: any }) => {
|
this.route.data.subscribe((data: { recurringDepositsAccountActionData: any }) => {
|
||||||
this.transactionAmount = data.recurringDepositsAccountActionData.amount;
|
this.transactionAmount = data.recurringDepositsAccountActionData.amount;
|
||||||
@@ -102,18 +126,43 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error(`Invalid transaction action: ${this.actionName}`);
|
throw new Error(`Invalid transaction action: ${this.actionName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.accountId = this.route.parent.snapshot.params['recurringDepositAccountId'];
|
this.accountId = this.route.parent.snapshot.params['recurringDepositAccountId'];
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.maxDate = this.settingsService.businessDate;
|
this.maxDate = this.settingsService.businessDate;
|
||||||
this.createdepositRecurringDepositForm();
|
this.createdepositRecurringDepositForm();
|
||||||
|
this.loadContextStrip();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadContextStrip(): void {
|
||||||
|
if (!this.accountId) return;
|
||||||
|
this.recurringDepositsService.getRecurringDepositsAccountData(this.accountId).subscribe({
|
||||||
|
next: (account: any) => {
|
||||||
|
this.accountNo = account?.accountNo ?? null;
|
||||||
|
this.clientName = account?.clientName ?? account?.groupName ?? null;
|
||||||
|
this.clientId = account?.clientId ?? null;
|
||||||
|
if (!this.currency && account?.currency) this.currency = account.currency;
|
||||||
|
this.availableBalance = Number(account?.summary?.accountBalance ?? account?.summary?.availableBalance ?? 0);
|
||||||
|
if (account?.clientId) {
|
||||||
|
this.fetchClientKyc(String(account.clientId));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: () => {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchClientKyc(clientId: string): void {
|
||||||
|
this.clientsService.getClientKyc(clientId).subscribe({
|
||||||
|
next: (kyc: any) => {
|
||||||
|
const pan = !!kyc?.panVerified;
|
||||||
|
const aadhaar = !!kyc?.aadhaarVerified;
|
||||||
|
this.kycStatus = pan && aadhaar ? 'Verified' : pan || aadhaar ? 'Partial' : 'Pending';
|
||||||
|
},
|
||||||
|
error: () => {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the deposits form.
|
|
||||||
*/
|
|
||||||
createdepositRecurringDepositForm() {
|
createdepositRecurringDepositForm() {
|
||||||
this.depositRecurringDepositForm = this.formBuilder.group({
|
this.depositRecurringDepositForm = this.formBuilder.group({
|
||||||
transactionDate: [
|
transactionDate: [
|
||||||
@@ -132,9 +181,71 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ── Denomination helpers ────────────────────────────────────────────
|
||||||
* Add payment detail fields to the UI.
|
denomKey(d: Denomination): string {
|
||||||
*/
|
return `${d.kind}:${d.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
countFor(d: Denomination): number {
|
||||||
|
return this.denominationCounts[this.denomKey(d)] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
subtotalFor(d: Denomination): number {
|
||||||
|
return this.countFor(d) * d.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDenomChange(d: Denomination, raw: any): void {
|
||||||
|
const n = Math.max(0, Math.floor(Number(raw) || 0));
|
||||||
|
this.denominationCounts[this.denomKey(d)] = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
bumpDenom(d: Denomination, delta: number): void {
|
||||||
|
const next = Math.max(0, this.countFor(d) + delta);
|
||||||
|
this.denominationCounts[this.denomKey(d)] = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
get notesTotal(): number {
|
||||||
|
return this.notes.reduce((s, d) => s + this.subtotalFor(d), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get coinsTotal(): number {
|
||||||
|
return this.coins.reduce((s, d) => s + this.subtotalFor(d), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get denominationTotal(): number {
|
||||||
|
return this.notesTotal + this.coinsTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
get totalPieces(): number {
|
||||||
|
return Object.values(this.denominationCounts).reduce((s, n) => s + (n || 0), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get denominationMatchesForm(): boolean {
|
||||||
|
const formAmt = Number(this.depositRecurringDepositForm?.controls?.['transactionAmount']?.value || 0);
|
||||||
|
return this.denominationTotal > 0 && formAmt === this.denominationTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyDenominationTotal(): void {
|
||||||
|
this.depositRecurringDepositForm.controls['transactionAmount'].setValue(this.denominationTotal);
|
||||||
|
this.depositRecurringDepositForm.controls['transactionAmount'].markAsDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
clearDenominations(): void {
|
||||||
|
this.denominationCounts = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
openIdentityVerify(): void {
|
||||||
|
if (!this.clientId) return;
|
||||||
|
this.identityVerified = true;
|
||||||
|
this.dialog.open(IdentityVerifyDialogComponent, {
|
||||||
|
width: '500px',
|
||||||
|
maxWidth: '92vw',
|
||||||
|
panelClass: 'idv-panel',
|
||||||
|
autoFocus: false,
|
||||||
|
data: { clientId: this.clientId, clientName: this.clientName, accountNo: this.accountNo }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
addPaymentDetails() {
|
addPaymentDetails() {
|
||||||
this.showPaymentDetails = !this.showPaymentDetails;
|
this.showPaymentDetails = !this.showPaymentDetails;
|
||||||
if (this.showPaymentDetails) {
|
if (this.showPaymentDetails) {
|
||||||
@@ -152,32 +263,51 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
submit() {
|
||||||
* Toggles the display of payment details
|
const formAmount = Number(this.depositRecurringDepositForm.value.transactionAmount) || 0;
|
||||||
*/
|
const paymentTypeId = this.depositRecurringDepositForm.value.paymentTypeId;
|
||||||
toggleDisplay() {
|
const paymentTypeLabel = this.paymentTypes?.find((p: any) => p.id === paymentTypeId)?.name ?? null;
|
||||||
this.showPaymentDetails = !this.showPaymentDetails;
|
|
||||||
|
const ref = this.dialog.open(TransactionConfirmDialogComponent, {
|
||||||
|
width: '480px',
|
||||||
|
maxWidth: '92vw',
|
||||||
|
panelClass: 'tcd-panel',
|
||||||
|
autoFocus: false,
|
||||||
|
data: {
|
||||||
|
kind: this.transactionType.deposit ? 'deposit' : 'withdrawal',
|
||||||
|
clientName: this.clientName,
|
||||||
|
accountNo: this.accountNo,
|
||||||
|
currentBalance: this.availableBalance,
|
||||||
|
transactionAmount: formAmount,
|
||||||
|
paymentTypeLabel
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ref.afterClosed().subscribe((result: any) => {
|
||||||
|
if (result?.confirm) {
|
||||||
|
this.postTransaction();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private postTransaction(): void {
|
||||||
* Submits the deposits recurring deposit form
|
const formData = this.depositRecurringDepositForm.value;
|
||||||
*/
|
|
||||||
submit() {
|
|
||||||
const depositRecurringDepositFormData = this.depositRecurringDepositForm.value;
|
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
const locale = this.settingsService.language.code;
|
const locale = this.settingsService.language.code;
|
||||||
const prevTransactionDate = this.depositRecurringDepositForm.value.transactionDate;
|
const prevDate = this.depositRecurringDepositForm.value.transactionDate;
|
||||||
if (depositRecurringDepositFormData.transactionDate instanceof Date) {
|
if (formData.transactionDate instanceof Date) {
|
||||||
depositRecurringDepositFormData.transactionDate = this.dateUtils.formatDate(prevTransactionDate, dateFormat);
|
formData.transactionDate = this.dateUtils.formatDate(prevDate, dateFormat);
|
||||||
}
|
}
|
||||||
const data = {
|
const data = { ...formData, dateFormat, locale };
|
||||||
...depositRecurringDepositFormData,
|
|
||||||
dateFormat,
|
|
||||||
locale
|
|
||||||
};
|
|
||||||
|
|
||||||
delete data['note'];
|
delete data['note'];
|
||||||
data['transactionAmount'] = data['transactionAmount'] * 1;
|
data['transactionAmount'] = data['transactionAmount'] * 1;
|
||||||
|
if (this.denominationTotal > 0) {
|
||||||
|
data['denominationBreakdown'] = [
|
||||||
|
...this.notes,
|
||||||
|
...this.coins
|
||||||
|
]
|
||||||
|
.map((d) => ({ denomination: d.value, kind: d.kind, count: this.countFor(d), subtotal: this.subtotalFor(d) }))
|
||||||
|
.filter((row) => row.count > 0);
|
||||||
|
}
|
||||||
this.recurringDepositsService
|
this.recurringDepositsService
|
||||||
.executeRecurringDepositsAccountCommand(this.accountId, this.action, data)
|
.executeRecurringDepositsAccountCommand(this.accountId, this.action, data)
|
||||||
.subscribe(() => {
|
.subscribe(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user