2476 lines
118 KiB
HTML
2476 lines
118 KiB
HTML
<!--
|
||
Copyright since 2025 Mifos Initiative
|
||
|
||
This Source Code Form is subject to the terms of the Mozilla Public
|
||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
-->
|
||
|
||
<div class="te-root">
|
||
<!-- ── Clerk edit: rejection reason banner ───────────────────────────── -->
|
||
@if (editMode && editRequest?.rejection_reason) {
|
||
<div class="te-reject-banner">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="12" r="10" />
|
||
<line x1="12" y1="8" x2="12" y2="12" />
|
||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||
</svg>
|
||
<div>
|
||
<strong>This request was rejected by the manager.</strong>
|
||
<span>Reason: {{ editRequest.rejection_reason }}</span>
|
||
<small>Update the details below and click “Resubmit for Approval”.</small>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── Cashier Day-Status Blocking Banners ───────────────────────────── -->
|
||
@if (previousDayUnsettled) {
|
||
<div class="te-day-block te-day-block--danger">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<circle cx="12" cy="12" r="10" />
|
||
<line x1="12" y1="8" x2="12" y2="12" />
|
||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||
</svg>
|
||
<span>
|
||
<strong>TRANSACTIONS BLOCKED</strong> — Previous day cash settlement is pending
|
||
@if (unsettledSince) {
|
||
<em>(since {{ unsettledSince }})</em>
|
||
}
|
||
. Complete Cash Settlement before processing any transactions.
|
||
</span>
|
||
</div>
|
||
}
|
||
@if (!morningTransferReceived && !previousDayUnsettled) {
|
||
<div class="te-day-block te-day-block--warning">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||
<line x1="12" y1="9" x2="12" y2="13" />
|
||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||
</svg>
|
||
<span>
|
||
<strong>NO CASH ALLOCATED</strong> — Morning transfer not received. Request a morning transfer from the vault
|
||
before processing cash transactions.
|
||
</span>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── ENTRY GRID shown when no account loaded yet ────────────────── -->
|
||
@if (!accountDetails && !loadingAccount) {
|
||
<div class="ws-wrap">
|
||
<!-- ── Header ── -->
|
||
<div class="ws-header">
|
||
<div class="ws-header__left">
|
||
<div class="ws-header__icon">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="3" width="20" height="14" rx="2" />
|
||
<line x1="8" y1="21" x2="16" y2="21" />
|
||
<line x1="12" y1="17" x2="12" y2="21" />
|
||
</svg>
|
||
</div>
|
||
<span class="ws-header__title">Transaction Entry</span>
|
||
</div>
|
||
<div class="ws-header__right">
|
||
<button type="button" class="ws-btn ws-btn--ghost" (click)="openCustomerSearch()">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="11" cy="11" r="8" />
|
||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||
</svg>
|
||
Search Customer
|
||
</button>
|
||
<span class="ws-header__effdt">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
||
<line x1="16" y1="2" x2="16" y2="6" />
|
||
<line x1="8" y1="2" x2="8" y2="6" />
|
||
<line x1="3" y1="10" x2="21" y2="10" />
|
||
</svg>
|
||
Eff. Date
|
||
<input class="ws-header__date" type="date" [value]="todayIso" readonly />
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Row cards ── -->
|
||
<div class="ws-rows">
|
||
@for (row of rows; track $index; let i = $index) {
|
||
<div class="ws-card" [class.ws-card--active]="selectedRow === row" (click)="onRowSelect(row, i)">
|
||
<div class="ws-card__badge">{{ i + 1 }}</div>
|
||
@if (isClerk && rows.length > 1) {
|
||
<button
|
||
class="ws-card__del"
|
||
type="button"
|
||
(click)="$event.stopPropagation(); deleteRow(i)"
|
||
title="Remove this row"
|
||
>
|
||
×
|
||
</button>
|
||
}
|
||
|
||
<div class="ws-card__body">
|
||
<!-- ── Row 1: Classification | Account ── -->
|
||
<div class="ws-group-row">
|
||
<div class="ws-group">
|
||
<span class="ws-caption">Classification</span>
|
||
<div class="ws-group__fields">
|
||
<div class="ws-f ws-f--type">
|
||
<label class="ws-f__lbl" title="Transaction Type">TYPE</label>
|
||
<select
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.typ"
|
||
(click)="$event.stopPropagation()"
|
||
(change)="onTypChange(row)"
|
||
>
|
||
@for (t of typOptions; track t.value) {
|
||
<option [value]="t.value">{{ t.label }}</option>
|
||
}
|
||
</select>
|
||
</div>
|
||
<div class="ws-f ws-f--branch">
|
||
<label class="ws-f__lbl" title="Branch">BRANCH</label>
|
||
<select class="ws-f__ctl" [(ngModel)]="row.br" (click)="$event.stopPropagation()">
|
||
<option [ngValue]="1">001 · Main</option>
|
||
<option [ngValue]="2">002 · M.G. Road</option>
|
||
<option [ngValue]="14">014 · Station Rd</option>
|
||
<option [ngValue]="27">027 · Industrial</option>
|
||
</select>
|
||
</div>
|
||
@if (row.typ === 'DD') {
|
||
<div class="ws-f ws-f--branch">
|
||
<label class="ws-f__lbl ws-f__lbl--accent" title="Sponsor Bank">SPONSOR BANK</label>
|
||
<select
|
||
class="ws-f__ctl ws-f__ctl--accent"
|
||
[(ngModel)]="row.sponsorBankId"
|
||
(click)="$event.stopPropagation()"
|
||
>
|
||
@if (sponsorBanks.length === 0) {
|
||
<option [ngValue]="null">-- Select Bank --</option>
|
||
}
|
||
@for (sb of sponsorBanks; track sb.id) {
|
||
<option [ngValue]="sb.id">{{ sb.name }}</option>
|
||
}
|
||
</select>
|
||
</div>
|
||
}
|
||
<div class="ws-f ws-f--paytype">
|
||
<label class="ws-f__lbl ws-f__lbl--accent" title="Transaction Type">TXN TP.</label>
|
||
<select
|
||
class="ws-f__ctl ws-f__ctl--accent"
|
||
[(ngModel)]="row.paymentTypeId"
|
||
(click)="$event.stopPropagation()"
|
||
[class.ws-f__ctl--req]="!row.paymentTypeId"
|
||
>
|
||
@for (pt of paymentTypesForRow(row); track pt.id) {
|
||
<option [ngValue]="pt.id">{{ pt.name }}</option>
|
||
}
|
||
</select>
|
||
</div>
|
||
<div class="ws-f ws-f--actyp">
|
||
<label class="ws-f__lbl" title="Account Type">AC. TP.</label>
|
||
@if (acTypLockedByToken && row === selectedRow) {
|
||
<span
|
||
class="ws-f__ctl ws-f__ctl--locked"
|
||
title="Locked — filled from a token-driven request, not a normal account entry"
|
||
>{{ acTypLockedLabel }}</span
|
||
>
|
||
} @else {
|
||
<select
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.acTyp"
|
||
(click)="$event.stopPropagation()"
|
||
(change)="
|
||
row.acTyp === 'GL' &&
|
||
row.typ !== 'Disbursement' &&
|
||
row.typ !== 'Fund Transfer' &&
|
||
row.typ !== 'Enter Bank Transfer' &&
|
||
(row.dbCr = 'Cr')
|
||
"
|
||
>
|
||
@for (a of acTypOptionsFor(row); track a) {
|
||
<option [value]="a">{{ a }}</option>
|
||
}
|
||
</select>
|
||
}
|
||
</div>
|
||
@if (showsEntryType(row)) {
|
||
<div class="ws-f ws-f--paytype">
|
||
<label
|
||
class="ws-f__lbl ws-f__lbl--accent"
|
||
title="What this money was actually collected for — checked before a share application or DD issuance can adopt this voucher"
|
||
>ENTRY TYPE</label
|
||
>
|
||
<select
|
||
class="ws-f__ctl ws-f__ctl--accent"
|
||
[(ngModel)]="row.entryTypeCvId"
|
||
(click)="$event.stopPropagation()"
|
||
>
|
||
<option [ngValue]="null">Select…</option>
|
||
@for (t of entryTypeOptions; track t.id) {
|
||
<option [ngValue]="t.id">{{ t.name }}</option>
|
||
}
|
||
</select>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ws-vdivider"></div>
|
||
|
||
<div class="ws-group ws-group--flex">
|
||
<span class="ws-caption">Account</span>
|
||
<div class="ws-group__fields">
|
||
<div class="ws-f ws-f--acno">
|
||
<label class="ws-f__lbl" title="Account Number">A/C NO.</label>
|
||
<input
|
||
class="ws-f__ctl ws-f__ctl--mono"
|
||
[value]="row.acNo"
|
||
(input)="onAcNoInput(row, i, $any($event.target).value)"
|
||
(click)="$event.stopPropagation()"
|
||
(blur)="onAcNoBlur(row, i)"
|
||
[placeholder]="
|
||
isDdAccountDebitRow(row)
|
||
? 'Account to debit'
|
||
: row.typ === 'DD'
|
||
? ddGlMapping?.ddPayableGlCode || 'Auto GL'
|
||
: 'Account number'
|
||
"
|
||
[readonly]="
|
||
(row.typ === 'DD' && !isDdAccountDebitRow(row)) || (acTypLockedByToken && row === selectedRow)
|
||
"
|
||
[class.ws-f__ctl--readonly]="
|
||
(row.typ === 'DD' && !isDdAccountDebitRow(row)) || (acTypLockedByToken && row === selectedRow)
|
||
"
|
||
[title]="
|
||
acTypLockedByToken && row === selectedRow
|
||
? 'Locked — filled from a token-driven request, not a normal account entry'
|
||
: isDdAccountDebitRow(row)
|
||
? 'Member account the draft is funded from'
|
||
: row.typ === 'DD'
|
||
? 'Configured Sponsor DD Payable GL (Auto-selected)'
|
||
: 'Account number'
|
||
"
|
||
/>
|
||
</div>
|
||
<div class="ws-f ws-f--ecsno">
|
||
<label class="ws-f__lbl" title="ECS Account Number">ECS A/C NO.</label>
|
||
<input
|
||
class="ws-f__ctl ws-f__ctl--mono"
|
||
[(ngModel)]="row.ecsAcNo"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="ECS account"
|
||
/>
|
||
</div>
|
||
<div class="ws-f ws-f--dbcr">
|
||
<label class="ws-f__lbl" title="Debit / Credit">DR./CR.</label>
|
||
<div class="ws-seg" (click)="$event.stopPropagation()">
|
||
<button
|
||
type="button"
|
||
class="ws-seg__btn ws-seg__btn--dr"
|
||
[class.ws-seg__btn--active-dr]="row.dbCr === 'Dr'"
|
||
[disabled]="
|
||
row.acTyp === 'GL' &&
|
||
row.typ !== 'Disbursement' &&
|
||
row.typ !== 'Fund Transfer' &&
|
||
row.typ !== 'Enter Bank Transfer'
|
||
"
|
||
(click)="row.dbCr = 'Dr'"
|
||
>
|
||
DR
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="ws-seg__btn ws-seg__btn--cr"
|
||
[class.ws-seg__btn--active-cr]="row.dbCr === 'Cr'"
|
||
(click)="row.dbCr = 'Cr'"
|
||
>
|
||
CR
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Row 2: Payment instrument | Narration ── -->
|
||
<div class="ws-group-row ws-group-row--mt">
|
||
<div class="ws-group">
|
||
<span class="ws-caption">Payment instrument</span>
|
||
<div class="ws-group__fields">
|
||
<div class="ws-f ws-f--amt">
|
||
<label
|
||
class="ws-f__lbl"
|
||
[title]="row.acTyp === 'SHR' ? 'Number of Shares' : 'Transaction Amount'"
|
||
>
|
||
{{ row.acTyp === 'SHR' ? 'SHARE NO.' : 'AMOUNT' }}
|
||
@if (!isClerk && (activeWithdrawalRequest || activeClosureRequest)) {
|
||
<span class="ws-amt-locked-badge" title="Amount locked — approved request">🔒</span>
|
||
}
|
||
</label>
|
||
<div
|
||
class="ws-amt-wrap"
|
||
[class.ws-amt-wrap--cash]="isCashPayment && selectedRow === row"
|
||
[class.ws-amt-wrap--locked]="!isClerk && !!(activeWithdrawalRequest || activeClosureRequest)"
|
||
>
|
||
@if (row.acTyp !== 'SHR') {
|
||
<span class="ws-amt-prefix">₹</span>
|
||
}
|
||
<input
|
||
class="ws-f__ctl ws-amt-input"
|
||
type="number"
|
||
[value]="row.amount ?? ''"
|
||
(input)="row.amount = +$any($event.target).value; syncShareLinkedAmounts()"
|
||
(click)="$event.stopPropagation()"
|
||
[placeholder]="isMultiDdRow(row) ? 'Per draft' : row.acTyp === 'SHR' ? '0' : '0.00'"
|
||
[readonly]="
|
||
isMultiDdRow(row) || (!isClerk && !!(activeWithdrawalRequest || activeClosureRequest))
|
||
"
|
||
[class.ws-amt-input--locked]="
|
||
isMultiDdRow(row) || (!isClerk && !!(activeWithdrawalRequest || activeClosureRequest))
|
||
"
|
||
/>
|
||
</div>
|
||
@if (row.acTyp === 'SHR' && rowSharePrices[row.acNo]) {
|
||
<span class="ws-f__hint"
|
||
>= ₹{{ (row.amount ?? 0) * rowSharePrices[row.acNo] | number: '1.2-2' }} @ ₹{{
|
||
rowSharePrices[row.acNo] | number: '1.2-2'
|
||
}}/share</span
|
||
>
|
||
}
|
||
<!-- On a slip the face value belongs to each draft, so the row total is the sum of
|
||
them and is filled in by the dialog rather than typed here. A clerk-typed
|
||
figure could disagree with the drafts, and the account would be debited for
|
||
a total the drafts do not add up to. -->
|
||
@if (isMultiDdRow(row)) {
|
||
<span class="ws-f__hint">
|
||
{{
|
||
row.ddLines?.length
|
||
? 'Total of ' + row.ddLines?.length + ' drafts'
|
||
: 'Set per draft in Fill DD Details'
|
||
}}
|
||
</span>
|
||
}
|
||
</div>
|
||
@if (row.acTyp === 'SHR' && (row.typ === 'Enter Bank Transfer' || row.typ === 'Fund Transfer')) {
|
||
<div class="ws-f ws-f--cheqno">
|
||
<label class="ws-f__lbl" title="Board Resolution Number">RESOLUTION NO.</label>
|
||
<input
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.resolutionNumber"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="e.g. BR-2026-014"
|
||
/>
|
||
</div>
|
||
<div class="ws-f ws-f--cheqdt">
|
||
<label class="ws-f__lbl" title="Date of Meeting">MEETING DATE</label>
|
||
<input
|
||
class="ws-f__ctl"
|
||
type="date"
|
||
[(ngModel)]="row.meetingDate"
|
||
(click)="$event.stopPropagation()"
|
||
/>
|
||
</div>
|
||
} @else if (isClerk && row.typ !== 'Withdrawal Slip') {
|
||
<div class="ws-f ws-f--cheqno">
|
||
<label class="ws-f__lbl" title="Cheque Number">CHEQUE NO.</label>
|
||
<input
|
||
class="ws-f__ctl ws-f__ctl--mono"
|
||
[(ngModel)]="row.chequeNo"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="—"
|
||
/>
|
||
</div>
|
||
<div class="ws-f ws-f--cheqdt">
|
||
<label class="ws-f__lbl" title="Cheque Date">CHEQ. DATE</label>
|
||
<input
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.cheqDate"
|
||
placeholder="dd-mm-yyyy"
|
||
(click)="$event.stopPropagation()"
|
||
/>
|
||
</div>
|
||
}
|
||
<div class="ws-f ws-f--tok">
|
||
<label class="ws-f__lbl" title="Token Number">TOK. NO.</label>
|
||
<input
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.tokNo"
|
||
[readonly]="!isClerk && !!(activeWithdrawalRequest || activeClosureRequest)"
|
||
[class.ws-f__ctl--readonly]="!isClerk && !!(activeWithdrawalRequest || activeClosureRequest)"
|
||
placeholder="Enter token"
|
||
(blur)="onTokNoBlur(row)"
|
||
(click)="$event.stopPropagation()"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ws-vdivider"></div>
|
||
|
||
<div class="ws-group ws-group--flex">
|
||
<span class="ws-caption">Narration</span>
|
||
<div class="ws-group__fields">
|
||
<div class="ws-f ws-f--desc">
|
||
<label class="ws-f__lbl" title="Description / Narration">DESCRIPTION</label>
|
||
<input
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.description"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="Narration / remarks"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Row 3: DD Charges & GST — only for TYPE=DD, TXN TP=Cash, AC.TP=GL ── -->
|
||
@if (isDdRow(row)) {
|
||
<div class="ws-group-row ws-group-row--mt">
|
||
<div class="ws-group ws-group--flex">
|
||
<span class="ws-caption">DD Charges & GST — Admin Rate {{ ddGstPercentage }}%</span>
|
||
<div class="ws-group__fields">
|
||
<!-- How many drafts this one debit funds. Typed before the details are filled in,
|
||
because it is what decides how many beneficiary forms the dialog presents.
|
||
Offered only where the server can honour it: a member savings/current
|
||
account debit, never a GL or cash draft. -->
|
||
@if (canRowBeMultiDd(row)) {
|
||
<div class="ws-f ws-f--amt">
|
||
<label class="ws-f__lbl" title="Number of demand drafts to raise against this single debit"
|
||
>NO. OF DD</label
|
||
>
|
||
<div class="ws-amt-wrap">
|
||
<input
|
||
class="ws-f__ctl ws-amt-input"
|
||
type="number"
|
||
min="1"
|
||
[max]="maxDdSlipLines"
|
||
step="1"
|
||
[value]="ddCountFor(row)"
|
||
(input)="onDdCountChange(row, +$any($event.target).value)"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="1"
|
||
/>
|
||
</div>
|
||
@if (isMultiDdRow(row)) {
|
||
<span class="ws-f__hint">One debit · {{ ddCountFor(row) }} drafts</span>
|
||
}
|
||
</div>
|
||
}
|
||
<div class="ws-f ws-f--amt">
|
||
<label class="ws-f__lbl" title="DD issuance charges / commission">CHARGES</label>
|
||
<div class="ws-amt-wrap" [class.ws-amt-wrap--locked]="isMultiDdRow(row)">
|
||
<span class="ws-amt-prefix">₹</span>
|
||
<input
|
||
class="ws-f__ctl ws-amt-input"
|
||
type="number"
|
||
[value]="row.ddCharges ?? ''"
|
||
(input)="row.ddCharges = +$any($event.target).value"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="0.00"
|
||
[readonly]="isMultiDdRow(row)"
|
||
[class.ws-amt-input--locked]="isMultiDdRow(row)"
|
||
/>
|
||
</div>
|
||
<!-- Charges are levied per draft and can differ between them, so on a slip they
|
||
are entered against each draft and totalled here. -->
|
||
@if (isMultiDdRow(row)) {
|
||
<span class="ws-f__hint">Set per draft</span>
|
||
}
|
||
</div>
|
||
<div class="ws-f ws-f--amt">
|
||
<label class="ws-f__lbl" title="GST on charges, at the admin-configured rate">
|
||
GST ({{ ddGstPercentage }}%)
|
||
</label>
|
||
<div class="ws-amt-wrap ws-amt-wrap--locked">
|
||
<span class="ws-amt-prefix">₹</span>
|
||
<input
|
||
class="ws-f__ctl ws-amt-input ws-amt-input--locked"
|
||
type="text"
|
||
[value]="ddGstDisplayFor(row)"
|
||
readonly
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="ws-f ws-f--amt">
|
||
<label class="ws-f__lbl" title="Amount + Charges + GST, rounded up to the next rupee">
|
||
{{ isDdAccountDebitRow(row) ? 'TOTAL TO DEBIT' : 'TOTAL TO COLLECT' }}
|
||
</label>
|
||
<div class="ws-amt-wrap ws-amt-wrap--locked">
|
||
<span class="ws-amt-prefix">₹</span>
|
||
<input
|
||
class="ws-f__ctl ws-amt-input ws-amt-input--locked"
|
||
type="text"
|
||
[value]="ddTotalDisplayFor(row)"
|
||
readonly
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ws-f" style="margin-left: auto; align-self: flex-end">
|
||
<button
|
||
type="button"
|
||
class="te-dd-issue-btn"
|
||
[disabled]="saving"
|
||
(click)="openIssueWalkinDd(row); $event.stopPropagation()"
|
||
[title]="
|
||
isMultiDdRow(row)
|
||
? 'Opens one beneficiary form per draft; DD numbers are allocated on submission'
|
||
: isDdAccountDebitRow(row)
|
||
? 'Auto-selects next available leaflet and opens the beneficiary form'
|
||
: 'Auto-selects next available leaflet and opens walk-in purchaser form'
|
||
"
|
||
>
|
||
<mat-icon style="font-size: 18px; width: 18px; height: 18px">{{
|
||
row.ddDetails || row.ddLines?.length ? 'edit_note' : 'post_add'
|
||
}}</mat-icon>
|
||
<!-- Names the leaflet once details are captured, so the clerk can see at a
|
||
glance which rows are ready before pressing Save. A slip names the count
|
||
instead: its numbers are not allocated until it is submitted. -->
|
||
<span>{{
|
||
row.ddLines?.length
|
||
? row.ddLines?.length + ' Drafts Captured · Edit Slip'
|
||
: row.ddDetails
|
||
? 'DD ' + row.ddDetails.ddNumber + ' · Edit Details'
|
||
: isMultiDdRow(row)
|
||
? 'Fill ' + ddCountFor(row) + ' DD Details'
|
||
: 'Fill DD Details'
|
||
}}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── DD GL UNCONFIGURED WARNING BANNER ────────────────────────── -->
|
||
@if (selectedRow?.typ === 'DD' && !isDdGlMappingConfigured) {
|
||
<div class="ws-dd-unconfigured-banner">
|
||
<mat-icon>warning</mat-icon>
|
||
<div class="ws-dd-unconfigured-banner__text">
|
||
<strong>DD GL Mapping Required:</strong> Demand Draft GL accounts have not been configured yet. Please ask an
|
||
administrator to set up the mapping under <em>Admin > Sponsor DD GST / GL Settings</em> before issuing
|
||
drafts.
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── APPROVED WITHDRAWAL BANNER (cashier disburse flow) ─────────── -->
|
||
@if (!isClerk && loadingWithdrawal) {
|
||
<div class="ws-withdrawal-banner ws-withdrawal-banner--loading">
|
||
<div class="ws-withdrawal-banner__spinner"></div>
|
||
<span>Looking up approved withdrawal request…</span>
|
||
</div>
|
||
}
|
||
@if (!isClerk && activeWithdrawalRequest) {
|
||
<div class="ws-withdrawal-banner">
|
||
<mat-icon class="ws-withdrawal-banner__icon">check_circle</mat-icon>
|
||
<div class="ws-withdrawal-banner__content">
|
||
<div class="ws-withdrawal-banner__title">Approved Withdrawal Request Linked</div>
|
||
<div class="ws-withdrawal-banner__details">
|
||
<span
|
||
>Token: <strong>{{ activeWithdrawalRequest.tok_no }}</strong></span
|
||
>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span
|
||
>Amount: <strong>₹{{ activeWithdrawalRequest.amount | number: '1.2-2' }}</strong></span
|
||
>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span
|
||
>Client: <strong>{{ activeWithdrawalRequest.client_name }}</strong></span
|
||
>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span>By Clerk: {{ activeWithdrawalRequest.clerk_name }}</span>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span>Approved by: {{ activeWithdrawalRequest.manager_name || '—' }}</span>
|
||
</div>
|
||
<div class="ws-withdrawal-banner__note">
|
||
After transaction succeeds, this request will be automatically marked <strong>WITHDRAWN</strong>.
|
||
</div>
|
||
</div>
|
||
<button type="button" class="ws-withdrawal-banner__clear" (click)="activeWithdrawalRequest = null" title="Unlink">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
</div>
|
||
}
|
||
@if (isClerk && pendingClosure) {
|
||
<div class="ws-withdrawal-banner ws-withdrawal-banner--closure">
|
||
<mat-icon class="ws-withdrawal-banner__icon">lock</mat-icon>
|
||
<div class="ws-withdrawal-banner__content">
|
||
<div class="ws-withdrawal-banner__title">Closing This Account</div>
|
||
<div class="ws-withdrawal-banner__details">
|
||
@if (closurePreview) {
|
||
<span
|
||
>Balance: <strong>₹{{ closurePreview.balance | number: '1.2-2' }}</strong></span
|
||
>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span
|
||
>Interest: <strong>+₹{{ closurePreview.interest | number: '1.2-2' }}</strong></span
|
||
>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span
|
||
>Payable: <strong>₹{{ closurePreview.total | number: '1.2-2' }}</strong></span
|
||
>
|
||
}
|
||
</div>
|
||
<div class="ws-withdrawal-banner__note">
|
||
Enter <strong>TOK. NO.</strong> below and press <strong>Save</strong> to send this for the Bank Manager's
|
||
approval. The account closes when the cashier hands over the cash.
|
||
</div>
|
||
</div>
|
||
<button type="button" class="ws-withdrawal-banner__clear" (click)="clearPendingClosure()" title="Cancel closure">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
</div>
|
||
}
|
||
@if (!isClerk && activeClosureRequest) {
|
||
<div class="ws-withdrawal-banner">
|
||
<mat-icon class="ws-withdrawal-banner__icon">lock</mat-icon>
|
||
<div class="ws-withdrawal-banner__content">
|
||
<div class="ws-withdrawal-banner__title">Approved Account Closure Linked</div>
|
||
<div class="ws-withdrawal-banner__details">
|
||
<span
|
||
>Token: <strong>{{ activeClosureRequest.tok_no }}</strong></span
|
||
>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span
|
||
>Cash to Hand Over:
|
||
<strong>₹{{ activeClosureRequest.cashier_disbursed_amount | number: '1.2-2' }}</strong></span
|
||
>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span
|
||
>Client: <strong>{{ activeClosureRequest.client_name }}</strong></span
|
||
>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span>By Clerk: {{ activeClosureRequest.clerk_name }}</span>
|
||
<span class="ws-wb-sep">·</span>
|
||
<span>Approved by: {{ activeClosureRequest.manager_name || '—' }}</span>
|
||
</div>
|
||
<div class="ws-withdrawal-banner__note">
|
||
@if (activeClosureRequest.rounding_amount && activeClosureRequest.rounding_amount !== 0) {
|
||
Exact closing balance was ₹{{ activeClosureRequest.closing_amount | number: '1.2-2' }} — rounded
|
||
{{ activeClosureRequest.rounding_amount > 0 ? 'up' : 'down' }} to whole rupees for cash.
|
||
} @else {
|
||
The account is already closed. Hand over the cash to complete the request.
|
||
}
|
||
</div>
|
||
</div>
|
||
<button type="button" class="ws-withdrawal-banner__clear" (click)="activeClosureRequest = null" title="Unlink">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── STATUS ────────────────────────────────────────────────────────── -->
|
||
@if (loadingAccount) {
|
||
<div class="te-loading">
|
||
<div class="te-loading__spinner">
|
||
<svg width="56" height="56" viewBox="0 0 56 56" fill="none">
|
||
<circle cx="28" cy="28" r="24" stroke="#e2e8f0" stroke-width="4" />
|
||
<circle
|
||
cx="28"
|
||
cy="28"
|
||
r="24"
|
||
stroke="url(#te-grad)"
|
||
stroke-width="4"
|
||
stroke-linecap="round"
|
||
stroke-dasharray="40 112"
|
||
class="te-loading__arc"
|
||
/>
|
||
<defs>
|
||
<linearGradient id="te-grad" x1="0" y1="0" x2="56" y2="56" gradientUnits="userSpaceOnUse">
|
||
<stop offset="0%" stop-color="#2563eb" />
|
||
<stop offset="100%" stop-color="#7c3aed" />
|
||
</linearGradient>
|
||
</defs>
|
||
</svg>
|
||
<div class="te-loading__pulse"></div>
|
||
</div>
|
||
<span class="te-loading__title">Loading Account</span>
|
||
<span class="te-loading__sub">Fetching account details, please wait…</span>
|
||
</div>
|
||
}
|
||
@if (accountError && !loadingAccount) {
|
||
<div class="te-error">{{ accountError }}</div>
|
||
}
|
||
|
||
<!-- ── ACCOUNT DETAIL ─────────────────────────────────────────────────── -->
|
||
@if (accountDetails && !loadingAccount) {
|
||
<div class="te-detail">
|
||
<!-- Customer info panels — Clerk role only -->
|
||
@if (isClerk) {
|
||
<!-- ROW 1: Personal | Joint + Nominees | Other + Balances -->
|
||
<div class="te-detail-top">
|
||
<!-- Personal Information -->
|
||
<div class="te-panel">
|
||
<div class="te-panel__header">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="8" r="4" />
|
||
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
|
||
</svg>
|
||
Personal Information
|
||
</div>
|
||
<div class="te-panel__body">
|
||
<div class="te-field">
|
||
<span class="te-label">Branch</span><span class="te-value">{{ officeName }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">A/c No.</span
|
||
><span class="te-value te-mono">{{ accountDetails.accountNo }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Name</span><span class="te-value te-value-name">{{ clientName }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">S/D/W/H/O</span><span class="te-value">{{ guardianName }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Address</span><span class="te-value te-value-wrap">{{ clientAddress }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Email</span><span class="te-value">{{ clientEmail }}</span>
|
||
</div>
|
||
<div class="te-field-row">
|
||
<div class="te-field">
|
||
<span class="te-label">Last Txn</span><span class="te-value">{{ lastTransactionDate }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Type</span
|
||
><span class="te-value">{{ accountDetails.depositType?.value ?? selectedRow?.acTyp ?? '—' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="te-field-row">
|
||
<div class="te-field">
|
||
<span class="te-label">Opening Dt</span><span class="te-value">{{ openingDate }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Status</span
|
||
><span class="te-value">{{ accountDetails.status?.value ?? '—' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">CIF No.</span><span class="te-value te-mono">{{ cifNumber }}</span>
|
||
</div>
|
||
@if (isNonIndividual) {
|
||
<div class="te-field">
|
||
<span class="te-label">Constitution</span>
|
||
<span class="te-value">{{
|
||
nonPersonDetails?.constitution?.name ?? nonPersonDetails?.constitutionType?.name ?? '—'
|
||
}}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Incorp. No.</span>
|
||
<span class="te-value te-mono">{{
|
||
nonPersonDetails?.incorpNo ?? nonPersonDetails?.incorporationNo ?? '—'
|
||
}}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Main Business</span>
|
||
<span class="te-value">{{
|
||
nonPersonDetails?.mainBusinessLine?.name ?? nonPersonDetails?.mainBusinessLineType?.name ?? '—'
|
||
}}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Joint Holders + Nominees -->
|
||
<div class="te-panel">
|
||
<div class="te-panel__header">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
||
<circle cx="9" cy="7" r="4" />
|
||
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
||
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||
</svg>
|
||
Joint A/c Holders
|
||
@if (accountDetails?.modeOfOperation) {
|
||
<span class="te-mandate-badge">{{ modeOfOperationLabel }}</span>
|
||
}
|
||
</div>
|
||
<div class="te-panel__body">
|
||
@if (jointHolders.length === 0) {
|
||
<span class="te-dim">No joint holders</span>
|
||
}
|
||
@for (jh of jointHolders; track jh.id ?? $index; let i = $index) {
|
||
<div
|
||
class="te-joint-row te-joint-row--clickable"
|
||
(click)="toggleHolder(i, jh)"
|
||
title="Click to view details"
|
||
>
|
||
<span class="te-joint-name">{{ jh.displayName }}</span>
|
||
<span class="te-badge" [class.te-badge--primary]="jh.isPrimary || jh.primary">
|
||
{{ jh.role ?? (jh.isPrimary || jh.primary ? 'Primary' : 'Co-Holder') }}
|
||
</span>
|
||
@if (jh.sharePercentage) {
|
||
<span class="te-dim">{{ jh.sharePercentage }}%</span>
|
||
}
|
||
@if (jh.signingAuthority) {
|
||
<span
|
||
class="te-sign-badge"
|
||
[class.te-sign-badge--mandatory]="jh.signingAuthority?.toLowerCase() === 'mandatory'"
|
||
[class.te-sign-badge--optional]="jh.signingAuthority?.toLowerCase() === 'optional'"
|
||
>
|
||
{{ jh.signingAuthority }}
|
||
</span>
|
||
}
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2">
|
||
<circle cx="11" cy="11" r="8" />
|
||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||
</svg>
|
||
</div>
|
||
}
|
||
|
||
<!-- Withdrawal rule alert -->
|
||
@if (withdrawalRule) {
|
||
<div
|
||
class="te-withdrawal-rule"
|
||
[class.te-withdrawal-rule--danger]="withdrawalRule.type === 'danger'"
|
||
[class.te-withdrawal-rule--warning]="withdrawalRule.type === 'warning'"
|
||
>
|
||
<span class="te-wr-icon">{{ withdrawalRule.icon }}</span>
|
||
<span class="te-wr-msg">{{ withdrawalRule.msg }}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<!-- Nominees (Individual) / Non-Individual Account Details -->
|
||
@if (!isNonIndividual) {
|
||
<div class="te-panel__header te-panel__header--mt">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
||
</svg>
|
||
Nominees
|
||
</div>
|
||
<div class="te-panel__body">
|
||
@if (nominees.length === 0) {
|
||
<span class="te-dim">No nominees</span>
|
||
}
|
||
@for (n of nominees; track n.id ?? $index) {
|
||
<div class="te-joint-row">
|
||
<span class="te-joint-name">{{ n.nomineeDisplayName ?? '—' }}</span>
|
||
<span class="te-dim">{{ n.relationshipName ?? '—' }}</span>
|
||
@if (n.percentageShare) {
|
||
<span class="te-badge">{{ n.percentageShare }}%</span>
|
||
}
|
||
@if (n.nomineeIsMinor) {
|
||
<span class="te-badge" style="background: #fff7ed; color: #c2410c; border-color: #fed7aa"
|
||
>Minor</span
|
||
>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@if (isNonIndividual) {
|
||
<div class="te-panel__header te-panel__header--mt">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="7" width="20" height="14" rx="2" />
|
||
<path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" />
|
||
</svg>
|
||
Non-Individual Account Details
|
||
@if (accountDetails?.modeOfOperation) {
|
||
<span class="te-mandate-badge">{{ modeOfOperationLabel }}</span>
|
||
}
|
||
</div>
|
||
<div class="te-panel__body">
|
||
@if (!accountDetails?.partners?.length) {
|
||
<span class="te-dim">No partners / members linked</span>
|
||
}
|
||
@for (p of accountDetails?.partners ?? []; track p.id; let i = $index) {
|
||
<div
|
||
class="te-joint-row te-joint-row--clickable"
|
||
(click)="
|
||
toggleHolder(i + 1000, {
|
||
clientId: p.clientId,
|
||
displayName: p.clientName,
|
||
cifNumber: p.clientCif,
|
||
sharePercentage: p.sharePercentage,
|
||
signingAuthority: p.isMandatory ? 'Mandatory' : 'Optional',
|
||
role: 'Partner',
|
||
isPrimary: false
|
||
})
|
||
"
|
||
title="Click to view details"
|
||
>
|
||
<span class="te-joint-name">{{ p.clientName ?? '—' }}</span>
|
||
<span class="te-dim te-mono" style="font-size: 10px">{{ p.clientCif ?? '' }}</span>
|
||
@if (p.sharePercentage) {
|
||
<span class="te-dim">{{ p.sharePercentage }}%</span>
|
||
}
|
||
@if (p.isMandatory) {
|
||
<span class="te-sign-badge te-sign-badge--mandatory">Mandatory</span>
|
||
} @else {
|
||
<span class="te-sign-badge te-sign-badge--optional">Optional</span>
|
||
}
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2">
|
||
<circle cx="11" cy="11" r="8" />
|
||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||
</svg>
|
||
</div>
|
||
}
|
||
@if (withdrawalRule && isNonIndividual) {
|
||
<div
|
||
class="te-withdrawal-rule"
|
||
[class.te-withdrawal-rule--danger]="withdrawalRule.type === 'danger'"
|
||
[class.te-withdrawal-rule--warning]="withdrawalRule.type === 'warning'"
|
||
>
|
||
<span class="te-wr-icon">{{ withdrawalRule.icon }}</span>
|
||
<span class="te-wr-msg">{{ withdrawalRule.msg }}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<!-- Other Info + KYC + Account Balances -->
|
||
<div class="te-panel">
|
||
<div class="te-panel__header">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
||
<line x1="16" y1="2" x2="16" y2="6" />
|
||
<line x1="8" y1="2" x2="8" y2="6" />
|
||
<line x1="3" y1="10" x2="21" y2="10" />
|
||
</svg>
|
||
Other Information
|
||
</div>
|
||
<div class="te-panel__body">
|
||
<div class="te-field">
|
||
<span class="te-label">Phone No.</span><span class="te-value">{{ clientPhone }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Client ID</span
|
||
><span class="te-value te-mono">{{ accountDetails.clientId }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">PAN</span><span class="te-value te-mono">{{ panNumber }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">KYC Status</span>
|
||
<span class="te-value" [style.color]="kycDetails?.kycStatus === 300 ? '#059669' : '#f59e0b'">{{
|
||
kycStatus
|
||
}}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">CKYC No.</span><span class="te-value te-mono">{{ ckycNumber }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Aadhaar</span><span class="te-value te-mono">{{ aadhaarNumber }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Product</span>
|
||
<span class="te-value">{{
|
||
accountDetails.savingsProductName ||
|
||
accountDetails.depositProductName ||
|
||
accountDetails.productName ||
|
||
'—'
|
||
}}</span>
|
||
</div>
|
||
@if (selectedRow?.acTyp === 'SHR') {
|
||
<div class="te-field">
|
||
<span class="te-label">No. of Shares Held</span
|
||
><span class="te-value te-mono">{{ accountDetails.summary?.totalApprovedShares ?? 0 }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Market Price / Share</span
|
||
><span class="te-value te-mono"
|
||
>{{ currencySymbol }}{{ accountDetails.currentMarketPrice | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Resolution No.</span
|
||
><span class="te-value te-mono">{{ accountDetails.resolutionNumber || '—' }}</span>
|
||
</div>
|
||
<div class="te-field">
|
||
<span class="te-label">Linked Savings A/C No.</span
|
||
><span class="te-value te-mono">{{ accountDetails.savingsAccountNumber || '—' }}</span>
|
||
</div>
|
||
@if (accountDetails.linkedLoanAccountNo) {
|
||
<div class="te-field">
|
||
<span class="te-label">Linked Loan A/C No.</span
|
||
><span class="te-value te-mono">{{ accountDetails.linkedLoanAccountNo }}</span>
|
||
</div>
|
||
}
|
||
}
|
||
</div>
|
||
|
||
<div class="te-panel__header te-panel__header--mt">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<line x1="12" y1="1" x2="12" y2="23" />
|
||
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
||
</svg>
|
||
Account Information
|
||
</div>
|
||
<div class="te-panel__body">
|
||
<div class="te-balance-row">
|
||
<span class="te-bal-num">1.</span>
|
||
<span class="te-bal-label">{{
|
||
selectedRow?.acTyp === 'SHR'
|
||
? 'Share Balance'
|
||
: accountDetails?.glCode
|
||
? 'GL Balance'
|
||
: 'Current Balance'
|
||
}}</span>
|
||
<span class="te-bal-value te-bal-value--cr"
|
||
>{{ currencySymbol }}{{ currentBalance | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
<div class="te-balance-row">
|
||
<span class="te-bal-num">2.</span><span class="te-bal-label">Today's Clg. Amount</span>
|
||
<span class="te-bal-value">{{ currencySymbol }}{{ todayClgAmount | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-balance-row">
|
||
<span class="te-bal-num">3.</span><span class="te-bal-label">Clear Balance (2-3)</span>
|
||
<span class="te-bal-value te-bal-value--cr"
|
||
>{{ currencySymbol }}{{ clearBalance | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
<div class="te-balance-row">
|
||
<span class="te-bal-num">4.</span><span class="te-bal-label">Freeze Amount</span>
|
||
<span class="te-bal-value">{{ currencySymbol }}{{ freezeAmount | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-balance-row">
|
||
<span class="te-bal-num">5.</span><span class="te-bal-label">Withdrawable Amt.</span>
|
||
<span class="te-bal-value te-bal-value--cr"
|
||
>{{ currencySymbol }}{{ withdrawableAmount | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── ALERTS PANEL ─────────────────────────────────────────────── -->
|
||
@if (accountAlerts.length > 0) {
|
||
<div class="te-alerts-wrap">
|
||
@for (alert of accountAlerts; track $index) {
|
||
<div
|
||
class="te-alert"
|
||
[class.te-alert--danger]="alert.type === 'danger'"
|
||
[class.te-alert--warning]="alert.type === 'warning'"
|
||
[class.te-alert--info]="alert.type === 'info'"
|
||
>
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
@if (alert.type === 'danger') {
|
||
<path
|
||
d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
|
||
/>
|
||
<line x1="12" y1="9" x2="12" y2="13" />
|
||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||
}
|
||
@if (alert.type === 'warning') {
|
||
<circle cx="12" cy="12" r="10" />
|
||
<line x1="12" y1="8" x2="12" y2="12" />
|
||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||
}
|
||
@if (alert.type === 'info') {
|
||
<circle cx="12" cy="12" r="10" />
|
||
<line x1="12" y1="16" x2="12" y2="12" />
|
||
<line x1="12" y1="8" x2="12.01" y2="8" />
|
||
}
|
||
</svg>
|
||
<span>{{ alert.message }}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- Balance info — Clerk mode (below full panels) -->
|
||
<div class="te-balance-bar">
|
||
<div class="te-bal-item">
|
||
<span class="te-bal-item__label">Net Balance</span>
|
||
<span class="te-bal-item__val te-bal-item__val--cr te-bal-item__val--big">
|
||
{{ currencySymbol }}{{ clearBalance | number: '1.2-2' }} CR
|
||
</span>
|
||
</div>
|
||
<div class="te-bal-item">
|
||
<span class="te-bal-item__label">Opening</span>
|
||
<span class="te-bal-item__val">{{ currencySymbol }}{{ currentBalance | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-bal-item">
|
||
<span class="te-bal-item__label">Cash Hold</span>
|
||
<span class="te-bal-item__val">{{ cashHold | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-bal-item">
|
||
<span class="te-bal-item__label">Transfer Hold</span>
|
||
<span class="te-bal-item__val">{{ freezeAmount | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-bal-item">
|
||
<span class="te-bal-item__label">Clearing Hold</span>
|
||
<span class="te-bal-item__val">{{ clearingHold | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-bal-item">
|
||
<span class="te-bal-item__label">After Trans.</span>
|
||
<span class="te-bal-item__val te-bal-item__val--cr"
|
||
>{{ currencySymbol }}{{ clearBalance | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
<div class="te-bal-item">
|
||
<span class="te-bal-item__label">Rate</span>
|
||
<span class="te-bal-item__val">{{ accountDetails.nominalAnnualInterestRate ?? 0 | number: '1.2-2' }}%</span>
|
||
</div>
|
||
<div class="te-bal-item">
|
||
<span class="te-bal-item__label">Currency</span>
|
||
<span class="te-bal-item__val">{{ accountDetails.currency?.code ?? 'INR' }}</span>
|
||
</div>
|
||
@if (canPrintStatement) {
|
||
<button type="button" class="te-btn" (click)="printStatement()">
|
||
<mat-icon>print</mat-icon> Statement
|
||
</button>
|
||
}
|
||
</div>
|
||
} @else {
|
||
<!-- Cashier: all account info in one compact strip -->
|
||
<div class="te-acs">
|
||
<!-- Identity -->
|
||
<div class="te-acs-id">
|
||
@if (clientPhotoUrl) {
|
||
<img
|
||
[src]="clientPhotoUrl"
|
||
class="te-acs-photo te-acs-photo--clickable"
|
||
alt="Photo"
|
||
title="Click to enlarge"
|
||
(click)="previewImage(clientPhotoUrl, 'Client Photo')"
|
||
/>
|
||
} @else {
|
||
<div class="te-acs-photo te-acs-photo--ph">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="1.5">
|
||
<circle cx="12" cy="8" r="4" />
|
||
<path d="M4 20c0-4 3.6-7 8-7s8 3 8 7" />
|
||
</svg>
|
||
</div>
|
||
}
|
||
<div class="te-acs-nameblock">
|
||
<span class="te-acs-name">{{
|
||
accountDetails?.glCode
|
||
? (accountDetails?.name ?? '—')
|
||
: (clientDetails?.displayName ?? accountDetails?.clientName ?? '—')
|
||
}}</span>
|
||
<span class="te-acs-acno">{{ accountDetails?.glCode ?? accountDetails?.accountNo }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="te-acs-sep"></div>
|
||
<!-- Account meta -->
|
||
<div class="te-acs-meta">
|
||
<span
|
||
class="te-acs-tag"
|
||
[class.te-acs-tag--active]="activeDividendClaimRequest || accountDetails?.status?.value === 'Active'"
|
||
[class.te-acs-tag--blocked]="accountDetails?.status?.value === 'Blocked'"
|
||
[class.te-acs-tag--dormant]="accountDetails?.subStatus?.value === 'Dormant'"
|
||
>{{
|
||
activeDividendClaimRequest
|
||
? 'Awaiting Disbursement'
|
||
: accountDetails?.subStatus?.value === 'Dormant'
|
||
? 'Dormant'
|
||
: (accountDetails?.status?.value ?? '—')
|
||
}}</span
|
||
>
|
||
<span class="te-acs-kv"
|
||
><b>A/c</b>
|
||
{{
|
||
accountDetails?.depositType?.value ||
|
||
(activeDividendClaimRequest
|
||
? 'Dividend Claim'
|
||
: selectedRow?.acTyp === 'CA'
|
||
? 'Current'
|
||
: selectedRow?.acTyp === 'SD'
|
||
? 'Savings'
|
||
: selectedRow?.acTyp === 'FD'
|
||
? 'Fixed Dep.'
|
||
: selectedRow?.acTyp === 'RD'
|
||
? 'Recurring'
|
||
: selectedRow?.acTyp === 'GL'
|
||
? (accountDetails?.type?.value ?? 'GL Account')
|
||
: selectedRow?.acTyp === 'SHR'
|
||
? 'Share'
|
||
: selectedRow?.acTyp === 'LA'
|
||
? 'Loan'
|
||
: '—')
|
||
}}</span
|
||
>
|
||
<span class="te-acs-kv"><b>Branch</b> {{ officeName ?? '—' }}</span>
|
||
<span class="te-acs-kv"
|
||
><b>Product</b>
|
||
{{
|
||
activeDividendClaimRequest
|
||
? 'Dividend Cash Payout'
|
||
: accountDetails?.savingsProductName ||
|
||
accountDetails?.depositProductName ||
|
||
accountDetails?.productName ||
|
||
'—'
|
||
}}</span
|
||
>
|
||
<span class="te-acs-kv"
|
||
><b>Rate</b> {{ accountDetails?.nominalAnnualInterestRate ?? 0 | number: '1.2-2' }}%</span
|
||
>
|
||
<span class="te-acs-kv"><b>CCY</b> {{ accountDetails?.currency?.code ?? 'INR' }}</span>
|
||
</div>
|
||
<div class="te-acs-sep"></div>
|
||
<!-- Balance figures -->
|
||
<div class="te-acs-bals">
|
||
<div class="te-acs-b te-acs-b--main">
|
||
<span class="te-acs-b-l">Net Bal</span>
|
||
<span class="te-acs-b-v te-acs-b-v--cr">{{ currencySymbol }}{{ clearBalance | number: '1.2-2' }} CR</span>
|
||
</div>
|
||
<div class="te-acs-b">
|
||
<span class="te-acs-b-l">Opening</span>
|
||
<span class="te-acs-b-v">{{ currencySymbol }}{{ currentBalance | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-acs-b">
|
||
<span class="te-acs-b-l">Cash Hold</span>
|
||
<span class="te-acs-b-v">{{ cashHold | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-acs-b">
|
||
<span class="te-acs-b-l">Tr. Hold</span>
|
||
<span class="te-acs-b-v">{{ freezeAmount | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-acs-b">
|
||
<span class="te-acs-b-l">Clr. Hold</span>
|
||
<span class="te-acs-b-v">{{ clearingHold | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-acs-b te-acs-b--after">
|
||
<span class="te-acs-b-l">After Trans.</span>
|
||
<span class="te-acs-b-v te-acs-b-v--cr">{{ currencySymbol }}{{ clearBalance | number: '1.2-2' }}</span>
|
||
</div>
|
||
</div>
|
||
@if (canPrintStatement) {
|
||
<div class="te-acs-sep"></div>
|
||
<button type="button" class="te-btn" (click)="printStatement()">
|
||
<mat-icon>print</mat-icon> Statement
|
||
</button>
|
||
}
|
||
<!-- Alerts -->
|
||
@if (accountAlerts.length > 0) {
|
||
<div class="te-acs-sep"></div>
|
||
<div class="te-acs-alerts">
|
||
@for (alert of accountAlerts; track alert.message) {
|
||
<span class="te-acs-alert te-acs-alert--{{ alert.type }}">
|
||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<circle cx="12" cy="12" r="10" />
|
||
<line x1="12" y1="8" x2="12" y2="12" />
|
||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||
</svg>
|
||
{{ alert.message }}
|
||
</span>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- ── DENOMINATION + OPENING/CLOSING — Cashier only ───────────── -->
|
||
@if (!isClerk) {
|
||
<div class="te-denom-wrap">
|
||
<!-- Opening Balance -->
|
||
<div class="te-panel te-panel--denom">
|
||
<div class="te-panel__header">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="12" cy="12" r="10" />
|
||
<polyline points="12 6 12 12 16 14" />
|
||
</svg>
|
||
Opening Position — {{ today }}
|
||
</div>
|
||
<div class="te-panel__body">
|
||
@if (!openingLoaded) {
|
||
<span class="te-dim">Loading…</span>
|
||
}
|
||
@if (openingLoaded && openingDenoms.length === 0) {
|
||
<span class="te-dim">No opening position found</span>
|
||
}
|
||
@if (openingLoaded && openingDenoms.length > 0) {
|
||
<table class="te-denom-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</th>
|
||
<th class="num">Opening Count</th>
|
||
<th class="num">Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (d of sortedOpeningDenoms; track d.denominationId) {
|
||
@if (d.count > 0) {
|
||
<tr>
|
||
<td>
|
||
<span class="te-denom-chip" [class.te-denom-chip--coin]="d.kind === 'coin'"
|
||
>₹{{ d.denominationValue }}</span
|
||
>
|
||
</td>
|
||
<td class="num">{{ d.count }}</td>
|
||
<td class="num te-cr">₹{{ d.count * d.denominationValue | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td><strong>Total</strong></td>
|
||
<td></td>
|
||
<td class="num te-cr">
|
||
<strong>₹{{ openingTotal | number: '1.2-2' }}</strong>
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Denomination Entry -->
|
||
<div class="te-panel te-panel--denom">
|
||
<div class="te-panel__header">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="7" width="20" height="14" rx="2" />
|
||
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" />
|
||
</svg>
|
||
Denomination Entry
|
||
@if (isSelectedRowDdCashGl) {
|
||
<span class="te-mandate-badge">Target ₹{{ ddTargetTotal | number: '1.2-2' }}</span>
|
||
} @else {
|
||
@if (denominationTotal > 0) {
|
||
<span class="te-mandate-badge">₹{{ denominationTotal | number: '1.2-2' }}</span>
|
||
}
|
||
@if (isCashPayment && denominationTotal <= 0) {
|
||
<span class="te-denom-required-badge">⚠ Required for Cash</span>
|
||
}
|
||
}
|
||
</div>
|
||
<div class="te-panel__body">
|
||
<table class="te-denom-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</th>
|
||
<th class="num">Count</th>
|
||
<th class="num">Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (d of allDenominations; track d.id) {
|
||
<tr>
|
||
<td>
|
||
<span class="te-denom-chip" [class.te-denom-chip--coin]="d.kind === 'coin'">
|
||
{{ d.label }}
|
||
</span>
|
||
</td>
|
||
<td class="num">
|
||
<div class="te-denom-stepper">
|
||
<button type="button" (click)="bumpDenom(d, -1)" [disabled]="countFor(d) === 0">−</button>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
[value]="countFor(d) || ''"
|
||
placeholder="0"
|
||
(input)="onDenomChange(d, $any($event.target).value)"
|
||
/>
|
||
<button type="button" (click)="bumpDenom(d, 1)">+</button>
|
||
</div>
|
||
</td>
|
||
<td class="num te-cr">
|
||
{{ subtotalFor(d) > 0 ? (subtotalFor(d) | number: '1.2-2') : '—' }}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td><strong>Total</strong></td>
|
||
<td></td>
|
||
<td class="num te-cr">
|
||
<strong>₹{{ denominationTotal | number: '1.2-2' }}</strong>
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
|
||
@if (isSelectedRowDdCashGl) {
|
||
<div class="te-denom-actions">
|
||
@if (denominationTotal <= 0) {
|
||
<span class="te-denom-required-badge"
|
||
>⚠ Count notes/coins totalling ₹{{ ddTargetTotal | number: '1.2-2' }}</span
|
||
>
|
||
} @else if (ddDenomMatchesTarget()) {
|
||
<span class="te-denom-match-badge"
|
||
>✓ Matches Total To Collect ₹{{ ddTargetTotal | number: '1.2-2' }}</span
|
||
>
|
||
} @else {
|
||
<span class="te-denom-required-badge">
|
||
⚠ Must total ₹{{ ddTargetTotal | number: '1.2-2' }} (counted ₹{{
|
||
denominationTotal | number: '1.2-2'
|
||
}})
|
||
</span>
|
||
}
|
||
<button class="te-btn" type="button" (click)="clearDenominations()">Clear</button>
|
||
</div>
|
||
} @else if (denominationTotal > 0) {
|
||
<div class="te-denom-actions">
|
||
<button class="te-btn te-btn--primary" type="button" (click)="applyDenominationTotal()">
|
||
Apply ₹{{ denominationTotal | number: '1.2-2' }} to Amount
|
||
</button>
|
||
<button class="te-btn" type="button" (click)="clearDenominations()">Clear</button>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Closing Position -->
|
||
<div class="te-panel te-panel--denom">
|
||
<div class="te-panel__header">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<polyline points="20 6 9 17 4 12" />
|
||
</svg>
|
||
Closing Position (Preview)
|
||
@if (selectedRow?.dbCr === 'Dr' && hasWithdrawalDenomOverdraft) {
|
||
<span class="te-denom-overdraft-badge">⚠ Stock Overdraft</span>
|
||
}
|
||
</div>
|
||
<div class="te-panel__body">
|
||
@if (selectedRow?.dbCr === 'Dr' && hasWithdrawalDenomOverdraft) {
|
||
<div class="te-denom-overdraft-alert">
|
||
<div class="te-denom-overdraft-alert__title">
|
||
<svg
|
||
width="14"
|
||
height="14"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="2.5"
|
||
>
|
||
<circle cx="12" cy="12" r="10" />
|
||
<line x1="12" y1="8" x2="12" y2="12" />
|
||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||
</svg>
|
||
Insufficient Denomination Stock — Cannot Proceed
|
||
</div>
|
||
<table class="te-denom-overdraft-alert__table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</th>
|
||
<th>Requested</th>
|
||
<th>Available</th>
|
||
<th>Short</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (o of denominationOverdrafts; track o.label) {
|
||
<tr>
|
||
<td>{{ o.label }}</td>
|
||
<td class="te-dr">{{ o.requested }}</td>
|
||
<td>{{ o.available }}</td>
|
||
<td class="te-dr">
|
||
<strong>−{{ o.shortfall }}</strong>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
<div class="te-denom-overdraft-alert__hint">Adjust denomination or request vault replenishment.</div>
|
||
</div>
|
||
}
|
||
@if (closingDenoms.length === 0) {
|
||
<span class="te-dim">Enter denomination to see closing position</span>
|
||
}
|
||
@if (closingDenoms.length > 0) {
|
||
<table class="te-denom-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</th>
|
||
<th class="num">Opening</th>
|
||
<th class="num">{{ selectedRow?.dbCr === 'Cr' ? '+Deposit' : '-Withdrawal' }}</th>
|
||
<th class="num">Closing</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (d of sortedClosingDenoms; track d.denominationValue + d.kind) {
|
||
<tr [class.te-closing-row--neg]="d.closingCount < 0">
|
||
<td>
|
||
<span class="te-denom-chip" [class.te-denom-chip--coin]="d.kind === 'coin'"
|
||
>₹{{ d.denominationValue }}</span
|
||
>
|
||
</td>
|
||
<td class="num">{{ d.openCount }}</td>
|
||
<td class="num" [style.color]="selectedRow?.dbCr === 'Cr' ? '#059669' : '#dc2626'">
|
||
{{ selectedRow?.dbCr === 'Cr' ? '+' : '-' }}{{ d.entered }}
|
||
</td>
|
||
<td class="num" [class.te-cr]="d.closingCount >= 0" [class.te-dr]="d.closingCount < 0">
|
||
{{ d.closingCount }}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr>
|
||
<td><strong>Total</strong></td>
|
||
<td class="num">₹{{ openingTotal | number: '1.2-2' }}</td>
|
||
<td class="num" [style.color]="selectedRow?.dbCr === 'Cr' ? '#059669' : '#dc2626'">
|
||
{{ selectedRow?.dbCr === 'Cr' ? '+' : '-' }}₹{{ denominationTotal | number: '1.2-2' }}
|
||
</td>
|
||
<td class="num" [class.te-cr]="closingTotal >= 0" [class.te-dr]="closingTotal < 0">
|
||
<strong>₹{{ closingTotal | number: '1.2-2' }}</strong>
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── ENTRY CARD (bottom — same design as top) ──────────────── -->
|
||
<div class="ws-wrap">
|
||
<div class="ws-header">
|
||
<div class="ws-header__left">
|
||
<div class="ws-header__icon">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="2" y="3" width="20" height="14" rx="2" />
|
||
<line x1="8" y1="21" x2="16" y2="21" />
|
||
<line x1="12" y1="17" x2="12" y2="21" />
|
||
</svg>
|
||
</div>
|
||
<span class="ws-header__title">Transaction Entry</span>
|
||
</div>
|
||
<div class="ws-header__right">
|
||
<button type="button" class="ws-btn ws-btn--ghost" (click)="openCustomerSearch()">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<circle cx="11" cy="11" r="8" />
|
||
<line x1="21" y1="21" x2="16.65" y2="16.65" />
|
||
</svg>
|
||
Search Customer
|
||
</button>
|
||
<span class="ws-header__effdt">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
||
<line x1="16" y1="2" x2="16" y2="6" />
|
||
<line x1="8" y1="2" x2="8" y2="6" />
|
||
<line x1="3" y1="10" x2="21" y2="10" />
|
||
</svg>
|
||
Eff. Date
|
||
<input class="ws-header__date" type="date" [value]="todayIso" readonly />
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ws-rows">
|
||
<fieldset class="ws-rows-fieldset" [disabled]="formReadOnly">
|
||
@for (row of rows; track $index; let i = $index) {
|
||
<div class="ws-card" [class.ws-card--active]="selectedRow === row" (click)="onRowSelect(row, i)">
|
||
<div class="ws-card__badge">{{ i + 1 }}</div>
|
||
<div class="ws-card__body">
|
||
<div class="ws-group-row">
|
||
<div class="ws-group">
|
||
<span class="ws-caption">Classification</span>
|
||
<div class="ws-group__fields">
|
||
<div class="ws-f ws-f--type">
|
||
<label class="ws-f__lbl">TYPE</label>
|
||
<select
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.typ"
|
||
(click)="$event.stopPropagation()"
|
||
(change)="onTypChange(row)"
|
||
>
|
||
@for (t of typOptions; track t.value) {
|
||
<option [value]="t.value">{{ t.label }}</option>
|
||
}
|
||
</select>
|
||
</div>
|
||
<div class="ws-f ws-f--branch">
|
||
<label class="ws-f__lbl">BRANCH</label>
|
||
<select class="ws-f__ctl" [(ngModel)]="row.br" (click)="$event.stopPropagation()">
|
||
<option [ngValue]="1">001 · Main</option>
|
||
<option [ngValue]="2">002 · M.G. Road</option>
|
||
<option [ngValue]="14">014 · Station Rd</option>
|
||
<option [ngValue]="27">027 · Industrial</option>
|
||
</select>
|
||
</div>
|
||
@if (row.typ === 'DD') {
|
||
<div class="ws-f ws-f--branch">
|
||
<label class="ws-f__lbl ws-f__lbl--accent" title="Sponsor Bank">SPONSOR BANK</label>
|
||
<select
|
||
class="ws-f__ctl ws-f__ctl--accent"
|
||
[(ngModel)]="row.sponsorBankId"
|
||
(click)="$event.stopPropagation()"
|
||
>
|
||
@if (sponsorBanks.length === 0) {
|
||
<option [ngValue]="null">-- Select Bank --</option>
|
||
}
|
||
@for (sb of sponsorBanks; track sb.id) {
|
||
<option [ngValue]="sb.id">{{ sb.name }}</option>
|
||
}
|
||
</select>
|
||
</div>
|
||
}
|
||
<div class="ws-f ws-f--paytype">
|
||
<label class="ws-f__lbl ws-f__lbl--accent">TXN TP.</label>
|
||
<select
|
||
class="ws-f__ctl ws-f__ctl--accent"
|
||
[(ngModel)]="row.paymentTypeId"
|
||
(click)="$event.stopPropagation()"
|
||
[class.ws-f__ctl--req]="!row.paymentTypeId"
|
||
>
|
||
@for (pt of paymentTypesForRow(row); track pt.id) {
|
||
<option [ngValue]="pt.id">{{ pt.name }}</option>
|
||
}
|
||
</select>
|
||
</div>
|
||
<div class="ws-f ws-f--actyp">
|
||
<label class="ws-f__lbl">AC. TP.</label>
|
||
@if (acTypLockedByToken && row === selectedRow) {
|
||
<span
|
||
class="ws-f__ctl ws-f__ctl--locked"
|
||
title="Locked — filled from a token-driven request, not a normal account entry"
|
||
>{{ acTypLockedLabel }}</span
|
||
>
|
||
} @else {
|
||
<select
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.acTyp"
|
||
(click)="$event.stopPropagation()"
|
||
(change)="
|
||
row.acTyp === 'GL' &&
|
||
row.typ !== 'Disbursement' &&
|
||
row.typ !== 'Fund Transfer' &&
|
||
row.typ !== 'Enter Bank Transfer' &&
|
||
(row.dbCr = 'Cr')
|
||
"
|
||
>
|
||
@for (a of acTypOptionsFor(row); track a) {
|
||
<option [value]="a">{{ a }}</option>
|
||
}
|
||
</select>
|
||
}
|
||
</div>
|
||
@if (showsEntryType(row)) {
|
||
<div class="ws-f ws-f--paytype">
|
||
<label
|
||
class="ws-f__lbl ws-f__lbl--accent"
|
||
title="What this money was actually collected for — checked before a share application or DD issuance can adopt this voucher"
|
||
>ENTRY TYPE</label
|
||
>
|
||
<select
|
||
class="ws-f__ctl ws-f__ctl--accent"
|
||
[(ngModel)]="row.entryTypeCvId"
|
||
(click)="$event.stopPropagation()"
|
||
>
|
||
<option [ngValue]="null">Select…</option>
|
||
@for (t of entryTypeOptions; track t.id) {
|
||
<option [ngValue]="t.id">{{ t.name }}</option>
|
||
}
|
||
</select>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
<div class="ws-vdivider"></div>
|
||
<div class="ws-group ws-group--flex">
|
||
<span class="ws-caption">Account</span>
|
||
<div class="ws-group__fields">
|
||
<div class="ws-f ws-f--acno">
|
||
<label class="ws-f__lbl">A/C NO.</label>
|
||
<input
|
||
class="ws-f__ctl ws-f__ctl--mono"
|
||
[value]="row.acNo"
|
||
(input)="onAcNoInput(row, i, $any($event.target).value)"
|
||
(blur)="onAcNoBlur(row, i)"
|
||
[placeholder]="
|
||
row.typ === 'DD' ? ddGlMapping?.ddPayableGlCode || 'Auto GL' : 'Account number'
|
||
"
|
||
[readonly]="row.typ === 'DD' || (acTypLockedByToken && row === selectedRow)"
|
||
[class.ws-f__ctl--readonly]="
|
||
row.typ === 'DD' || (acTypLockedByToken && row === selectedRow)
|
||
"
|
||
[title]="
|
||
acTypLockedByToken && row === selectedRow
|
||
? 'Locked — filled from a token-driven request, not a normal account entry'
|
||
: row.typ === 'DD'
|
||
? 'Configured Sponsor DD Payable GL (Auto-selected)'
|
||
: 'Account number'
|
||
"
|
||
/>
|
||
</div>
|
||
<div class="ws-f ws-f--ecsno">
|
||
<label class="ws-f__lbl">ECS A/C NO.</label>
|
||
<input
|
||
class="ws-f__ctl ws-f__ctl--mono"
|
||
[(ngModel)]="row.ecsAcNo"
|
||
placeholder="ECS account"
|
||
/>
|
||
</div>
|
||
<div class="ws-f ws-f--dbcr">
|
||
<label class="ws-f__lbl">DR./CR.</label>
|
||
<div class="ws-seg">
|
||
<button
|
||
type="button"
|
||
class="ws-seg__btn ws-seg__btn--dr"
|
||
[class.ws-seg__btn--active-dr]="row.dbCr === 'Dr'"
|
||
[disabled]="
|
||
row.acTyp === 'GL' &&
|
||
row.typ !== 'Disbursement' &&
|
||
row.typ !== 'Fund Transfer' &&
|
||
row.typ !== 'Enter Bank Transfer'
|
||
"
|
||
(click)="$event.stopPropagation(); row.dbCr = 'Dr'"
|
||
>
|
||
DR
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="ws-seg__btn ws-seg__btn--cr"
|
||
[class.ws-seg__btn--active-cr]="row.dbCr === 'Cr'"
|
||
(click)="$event.stopPropagation(); row.dbCr = 'Cr'"
|
||
>
|
||
CR
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="ws-group-row ws-group-row--mt">
|
||
<div class="ws-group">
|
||
<span class="ws-caption">Payment instrument</span>
|
||
<div class="ws-group__fields">
|
||
<div class="ws-f ws-f--amt">
|
||
<label class="ws-f__lbl">
|
||
{{ row.acTyp === 'SHR' ? 'SHARE NO.' : 'AMOUNT' }}
|
||
@if (!isClerk && (activeWithdrawalRequest || activeClosureRequest)) {
|
||
<span class="ws-amt-locked-badge" title="Amount locked — approved request">🔒</span>
|
||
}
|
||
</label>
|
||
<div
|
||
class="ws-amt-wrap"
|
||
[class.ws-amt-wrap--cash]="isCashPayment && selectedRow === row"
|
||
[class.ws-amt-wrap--locked]="
|
||
!isClerk && !!(activeWithdrawalRequest || activeClosureRequest)
|
||
"
|
||
>
|
||
@if (row.acTyp !== 'SHR') {
|
||
<span class="ws-amt-prefix">₹</span>
|
||
}
|
||
<input
|
||
class="ws-f__ctl ws-amt-input"
|
||
type="number"
|
||
[value]="row.amount ?? ''"
|
||
(input)="row.amount = +$any($event.target).value; syncShareLinkedAmounts()"
|
||
(click)="$event.stopPropagation()"
|
||
[placeholder]="isMultiDdRow(row) ? 'Per draft' : row.acTyp === 'SHR' ? '0' : '0.00'"
|
||
[readonly]="
|
||
isMultiDdRow(row) || (!isClerk && !!(activeWithdrawalRequest || activeClosureRequest))
|
||
"
|
||
[class.ws-amt-input--locked]="
|
||
isMultiDdRow(row) || (!isClerk && !!(activeWithdrawalRequest || activeClosureRequest))
|
||
"
|
||
/>
|
||
</div>
|
||
@if (row.acTyp === 'SHR' && rowSharePrices[row.acNo]) {
|
||
<span class="ws-f__hint"
|
||
>= ₹{{ (row.amount ?? 0) * rowSharePrices[row.acNo] | number: '1.2-2' }} @ ₹{{
|
||
rowSharePrices[row.acNo] | number: '1.2-2'
|
||
}}/share</span
|
||
>
|
||
}
|
||
<!-- On a slip the face value belongs to each draft, so the row total is the
|
||
sum of them and is filled in by the dialog rather than typed here. A
|
||
clerk-typed figure could disagree with the drafts, and the account would
|
||
be debited for a total the drafts do not add up to. -->
|
||
@if (isMultiDdRow(row)) {
|
||
<span class="ws-f__hint">
|
||
{{
|
||
row.ddLines?.length
|
||
? 'Total of ' + row.ddLines?.length + ' drafts'
|
||
: 'Set per draft in Fill DD Details'
|
||
}}
|
||
</span>
|
||
}
|
||
</div>
|
||
@if (
|
||
row.acTyp === 'SHR' && (row.typ === 'Enter Bank Transfer' || row.typ === 'Fund Transfer')
|
||
) {
|
||
<div class="ws-f ws-f--cheqno">
|
||
<label class="ws-f__lbl">RESOLUTION NO.</label>
|
||
<input
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.resolutionNumber"
|
||
placeholder="e.g. BR-2026-014"
|
||
/>
|
||
</div>
|
||
<div class="ws-f ws-f--cheqdt">
|
||
<label class="ws-f__lbl">MEETING DATE</label>
|
||
<input class="ws-f__ctl" type="date" [(ngModel)]="row.meetingDate" />
|
||
</div>
|
||
} @else if (isClerk) {
|
||
<div class="ws-f ws-f--cheqno">
|
||
<label class="ws-f__lbl">CHEQUE NO.</label>
|
||
<input class="ws-f__ctl ws-f__ctl--mono" [(ngModel)]="row.chequeNo" placeholder="—" />
|
||
</div>
|
||
<div class="ws-f ws-f--cheqdt">
|
||
<label class="ws-f__lbl">CHEQ. DATE</label>
|
||
<input class="ws-f__ctl" [(ngModel)]="row.cheqDate" placeholder="dd-mm-yyyy" />
|
||
</div>
|
||
}
|
||
<div class="ws-f ws-f--tok">
|
||
<label class="ws-f__lbl">TOK. NO.</label>
|
||
<input
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.tokNo"
|
||
[readonly]="!isClerk && !!(activeWithdrawalRequest || activeClosureRequest)"
|
||
[class.ws-f__ctl--readonly]="
|
||
!isClerk && !!(activeWithdrawalRequest || activeClosureRequest)
|
||
"
|
||
placeholder="Enter token"
|
||
(blur)="onTokNoBlur(row)"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="ws-vdivider"></div>
|
||
<div class="ws-group ws-group--flex">
|
||
<span class="ws-caption">Narration</span>
|
||
<div class="ws-group__fields">
|
||
<div class="ws-f ws-f--desc">
|
||
<label class="ws-f__lbl">DESCRIPTION</label>
|
||
<input
|
||
class="ws-f__ctl"
|
||
[(ngModel)]="row.description"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="Narration / remarks"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ── Row 3: DD Charges & GST — only for TYPE=DD, TXN TP=Cash, AC.TP=GL ── -->
|
||
@if (isDdRow(row)) {
|
||
<div class="ws-group-row ws-group-row--mt">
|
||
<div class="ws-group ws-group--flex">
|
||
<span class="ws-caption">DD Charges & GST — Admin Rate {{ ddGstPercentage }}%</span>
|
||
<div class="ws-group__fields">
|
||
<!-- How many drafts this one debit funds. Typed before the details are filled
|
||
in, because it is what decides how many beneficiary forms the dialog
|
||
presents. Offered only where the server can honour it: a member
|
||
savings/current account debit, never a GL or cash draft. -->
|
||
@if (canRowBeMultiDd(row)) {
|
||
<div class="ws-f ws-f--amt">
|
||
<label
|
||
class="ws-f__lbl"
|
||
title="Number of demand drafts to raise against this single debit"
|
||
>NO. OF DD</label
|
||
>
|
||
<div class="ws-amt-wrap">
|
||
<input
|
||
class="ws-f__ctl ws-amt-input"
|
||
type="number"
|
||
min="1"
|
||
[max]="maxDdSlipLines"
|
||
step="1"
|
||
[value]="ddCountFor(row)"
|
||
(input)="onDdCountChange(row, +$any($event.target).value)"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="1"
|
||
/>
|
||
</div>
|
||
@if (isMultiDdRow(row)) {
|
||
<span class="ws-f__hint">One debit · {{ ddCountFor(row) }} drafts</span>
|
||
}
|
||
</div>
|
||
}
|
||
<div class="ws-f ws-f--amt">
|
||
<label class="ws-f__lbl" title="DD issuance charges / commission">CHARGES</label>
|
||
<div class="ws-amt-wrap" [class.ws-amt-wrap--locked]="isMultiDdRow(row)">
|
||
<span class="ws-amt-prefix">₹</span>
|
||
<input
|
||
class="ws-f__ctl ws-amt-input"
|
||
type="number"
|
||
[value]="row.ddCharges ?? ''"
|
||
(input)="row.ddCharges = +$any($event.target).value"
|
||
(click)="$event.stopPropagation()"
|
||
placeholder="0.00"
|
||
[readonly]="isMultiDdRow(row)"
|
||
[class.ws-amt-input--locked]="isMultiDdRow(row)"
|
||
/>
|
||
</div>
|
||
<!-- Charges are levied per draft and can differ between them, so on a slip
|
||
they are entered against each draft and totalled here. -->
|
||
@if (isMultiDdRow(row)) {
|
||
<span class="ws-f__hint">Set per draft</span>
|
||
}
|
||
</div>
|
||
<div class="ws-f ws-f--amt">
|
||
<label class="ws-f__lbl" title="GST on charges, at the admin-configured rate">
|
||
GST ({{ ddGstPercentage }}%)
|
||
</label>
|
||
<div class="ws-amt-wrap ws-amt-wrap--locked">
|
||
<span class="ws-amt-prefix">₹</span>
|
||
<input
|
||
class="ws-f__ctl ws-amt-input ws-amt-input--locked"
|
||
type="text"
|
||
[value]="ddGstDisplayFor(row)"
|
||
readonly
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="ws-f ws-f--amt">
|
||
<label class="ws-f__lbl" title="Amount + Charges + GST, rounded up to the next rupee">
|
||
{{ isDdAccountDebitRow(row) ? 'TOTAL TO DEBIT' : 'TOTAL TO COLLECT' }}
|
||
</label>
|
||
<div class="ws-amt-wrap ws-amt-wrap--locked">
|
||
<span class="ws-amt-prefix">₹</span>
|
||
<input
|
||
class="ws-f__ctl ws-amt-input ws-amt-input--locked"
|
||
type="text"
|
||
[value]="ddTotalDisplayFor(row)"
|
||
readonly
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="ws-f" style="margin-left: auto; align-self: flex-end">
|
||
<button
|
||
type="button"
|
||
class="te-dd-issue-btn"
|
||
[disabled]="saving"
|
||
(click)="openIssueWalkinDd(row); $event.stopPropagation()"
|
||
[title]="
|
||
isMultiDdRow(row)
|
||
? 'Opens one beneficiary form per draft; DD numbers are allocated on submission'
|
||
: isDdAccountDebitRow(row)
|
||
? 'Auto-selects next available leaflet and opens the beneficiary form'
|
||
: 'Auto-selects next available leaflet and opens walk-in purchaser form'
|
||
"
|
||
>
|
||
<mat-icon style="font-size: 18px; width: 18px; height: 18px">{{
|
||
row.ddDetails || row.ddLines?.length ? 'edit_note' : 'post_add'
|
||
}}</mat-icon>
|
||
<!-- Names what has been captured, so the clerk can see at a glance which
|
||
rows are ready before pressing Save. A slip names its count instead:
|
||
its numbers are not allocated until it is submitted. -->
|
||
<span>{{
|
||
row.ddLines?.length
|
||
? row.ddLines?.length + ' Drafts Captured · Edit Slip'
|
||
: row.ddDetails
|
||
? 'DD ' + row.ddDetails.ddNumber + ' · Edit Details'
|
||
: isMultiDdRow(row)
|
||
? 'Fill ' + ddCountFor(row) + ' DD Details'
|
||
: 'Fill DD Details'
|
||
}}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</fieldset>
|
||
</div>
|
||
|
||
<!-- Action buttons footer -->
|
||
@if (reviewMode) {
|
||
<div class="ws-footer">
|
||
<div class="ws-footer__stats">
|
||
<button class="ws-btn ws-btn--ghost ws-btn--exit" type="button" (click)="backToInbox()">
|
||
← Back to Inbox
|
||
</button>
|
||
@if (reviewRequest) {
|
||
<span class="ws-review-status" [attr.data-status]="reviewRequest.status">
|
||
{{ reviewRequest.status }}
|
||
</span>
|
||
}
|
||
</div>
|
||
<div class="ws-footer__actions">
|
||
@if (reviewRequest?.status === 'PENDING') {
|
||
<button class="ws-btn ws-btn--reject" type="button" (click)="openRejectReview()" [disabled]="saving">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<line x1="18" y1="6" x2="6" y2="18" />
|
||
<line x1="6" y1="6" x2="18" y2="18" />
|
||
</svg>
|
||
Reject
|
||
</button>
|
||
<button class="ws-btn ws-btn--post" type="button" (click)="approveReview()" [disabled]="saving">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<polyline points="20 6 9 17 4 12" />
|
||
</svg>
|
||
{{ saving ? 'Processing…' : 'Approve & Post' }}
|
||
</button>
|
||
}
|
||
</div>
|
||
</div>
|
||
} @else if (editMode) {
|
||
<div class="ws-footer">
|
||
<div class="ws-footer__stats">
|
||
<button class="ws-btn ws-btn--ghost ws-btn--exit" type="button" (click)="cancelEdit()">
|
||
← My Requests
|
||
</button>
|
||
@if (editRequest) {
|
||
<span class="ws-review-status" [attr.data-status]="editRequest.status">
|
||
{{ editResubmittable ? 'EDITING REJECTED' : editRequest.status }}
|
||
</span>
|
||
}
|
||
</div>
|
||
<div class="ws-footer__actions">
|
||
@if (editResubmittable) {
|
||
<button class="ws-btn ws-btn--post" type="button" (click)="resubmitEdit()" [disabled]="saving">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<polyline points="20 6 9 17 4 12" />
|
||
</svg>
|
||
{{ saving ? 'Resubmitting…' : 'Resubmit for Approval' }}
|
||
</button>
|
||
}
|
||
</div>
|
||
</div>
|
||
} @else {
|
||
@if (isClerkCashDeposit) {
|
||
<div class="te-day-block te-day-block--warning">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||
<line x1="12" y1="9" x2="12" y2="13" />
|
||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||
</svg>
|
||
<span>
|
||
<strong>CASH DEPOSIT NOT PERMITTED</strong> — Cash deposits can only be processed by the cashier.
|
||
Notes/coins denomination is managed at the cashier vault.
|
||
</span>
|
||
</div>
|
||
}
|
||
<div class="ws-footer">
|
||
<div class="ws-footer__stats">
|
||
<button class="ws-btn ws-btn--ghost ws-btn--exit" type="button" (click)="onExit()">Exit</button>
|
||
<button class="ws-btn ws-btn--ghost" type="button" (click)="onCancel()">Cancel</button>
|
||
<button class="ws-btn ws-btn--ghost" type="button">Drop</button>
|
||
<button class="ws-btn ws-btn--ghost" type="button" (click)="onSearch()">Search</button>
|
||
<button class="ws-btn ws-btn--mode" type="button">Change Tr.Mode</button>
|
||
@if (isClerk) {
|
||
<button class="ws-btn ws-btn--add" type="button" (click)="addRow()" title="Add another transaction row">
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<line x1="12" y1="5" x2="12" y2="19" />
|
||
<line x1="5" y1="12" x2="19" y2="12" />
|
||
</svg>
|
||
Add
|
||
</button>
|
||
<button
|
||
class="ws-btn ws-btn--ghost"
|
||
type="button"
|
||
(click)="router.navigate(['/clerk/my-disbursement-requests'])"
|
||
title="View my fund requests — Disbursement, Collection, Fund Transfer and Bank Transfer"
|
||
>
|
||
Disbursements & Transfer
|
||
</button>
|
||
}
|
||
@if (canShowAccountActions) {
|
||
<button
|
||
class="ws-btn ws-btn--action"
|
||
type="button"
|
||
(click)="openAccountActions()"
|
||
title="Account actions for the loaded account"
|
||
>
|
||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
|
||
</svg>
|
||
Action
|
||
</button>
|
||
}
|
||
</div>
|
||
<div class="ws-footer__actions">
|
||
<button
|
||
class="ws-btn ws-btn--post"
|
||
type="button"
|
||
(click)="onSave()"
|
||
[disabled]="saving || isClerkCashDeposit"
|
||
>
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
||
<polyline points="20 6 9 17 4 12" />
|
||
</svg>
|
||
{{ saving ? 'Posting…' : 'Save' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<!-- ── ACCOUNT ACTIONS (clerk) ──────────────────────────────────────────── -->
|
||
@if (showAccountActions) {
|
||
<div class="te-confirm-overlay" (click)="closeAccountActions()">
|
||
<div class="te-confirm-box te-aa-box" (click)="$event.stopPropagation()">
|
||
<div class="te-confirm-header te-confirm-header--action">
|
||
<span class="te-confirm-icon">⚡</span>
|
||
<span>Account Actions</span>
|
||
</div>
|
||
<div class="te-confirm-body">
|
||
<div class="te-aa-account">
|
||
<span class="te-aa-account__no">{{ accountDetails?.accountNo }}</span>
|
||
<span class="te-aa-account__name">{{ clientName }}</span>
|
||
</div>
|
||
|
||
<label class="te-aa-check">
|
||
<input type="checkbox" [(ngModel)]="closeAccountChecked" (change)="onCloseAccountToggle()" />
|
||
<span>
|
||
<strong>Close this account</strong>
|
||
<small>Pays out the full balance in cash and closes the account, after manager approval.</small>
|
||
</span>
|
||
</label>
|
||
|
||
@if (closeAccountChecked) {
|
||
@if (closurePreviewLoading) {
|
||
<div class="te-aa-calc te-aa-calc--loading">Posting interest to the account…</div>
|
||
} @else if (closurePreviewError) {
|
||
<div class="te-aa-calc te-aa-calc--error">{{ closurePreviewError }}</div>
|
||
} @else if (closurePreview) {
|
||
<div class="te-aa-calc">
|
||
<div class="te-aa-calc__row">
|
||
<span>Current Balance</span>
|
||
<span>₹{{ closurePreview.balance | number: '1.2-2' }}</span>
|
||
</div>
|
||
@if (closurePreview.interest < 0) {
|
||
<!-- Negative only when this run corrects interest that was over-posted earlier: the
|
||
delta is against the stored balance, so calling it "interest" would read as the
|
||
member being charged. The Payable figure below is the authoritative one either way. -->
|
||
<div class="te-aa-calc__row">
|
||
<span>Interest correction</span>
|
||
<span>− ₹{{ -closurePreview.interest | number: '1.2-2' }}</span>
|
||
</div>
|
||
} @else {
|
||
<div class="te-aa-calc__row">
|
||
<span>Interest to date</span>
|
||
<span>+ ₹{{ closurePreview.interest | number: '1.2-2' }}</span>
|
||
</div>
|
||
}
|
||
<div class="te-aa-calc__row te-aa-calc__row--total">
|
||
<span>Payable on Closure</span>
|
||
<span>₹{{ closurePreview.total | number: '1.2-2' }}</span>
|
||
</div>
|
||
@if (!closurePreview.upToDate) {
|
||
<div class="te-aa-calc__warn">
|
||
Interest could not be calculated just now, so this shows the current balance only — the actual payout
|
||
will be higher once interest is added at approval.
|
||
</div>
|
||
}
|
||
<div class="te-aa-calc__note">
|
||
Interest has been posted to the account as of {{ closurePreview.asOfDate }} — this is the account's real
|
||
balance now, not an estimate. Cash is handed over rounded to whole rupees.
|
||
</div>
|
||
</div>
|
||
|
||
<div class="te-aa-next">
|
||
This amount will be filled into <strong>AMOUNT</strong> on the slip. Enter <strong>TOK. NO.</strong> and
|
||
press <strong>Save</strong> to send it for the Bank Manager's approval.
|
||
</div>
|
||
}
|
||
}
|
||
</div>
|
||
<div class="te-confirm-actions">
|
||
<button class="te-btn" type="button" (click)="closeAccountActions()">Cancel</button>
|
||
<button
|
||
class="te-btn te-btn--primary"
|
||
type="button"
|
||
[disabled]="closeAccountChecked && (!closurePreview || closurePreviewLoading)"
|
||
(click)="applyCloseAccount()"
|
||
>
|
||
Save
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── REJECT DIALOG (manager review) ───────────────────────────────────── -->
|
||
@if (showRejectDialog) {
|
||
<div class="te-confirm-overlay" (click)="cancelRejectReview()">
|
||
<div class="te-confirm-box" (click)="$event.stopPropagation()">
|
||
<div class="te-confirm-header te-confirm-header--withdrawal">
|
||
<span class="te-confirm-icon">✕</span>
|
||
<span>Reject Withdrawal Request</span>
|
||
</div>
|
||
<div class="te-confirm-body">
|
||
<p class="ws-reject-hint">
|
||
Please provide a reason. The request will be sent back to the clerk for correction and resubmission.
|
||
</p>
|
||
<textarea
|
||
class="ws-reject-textarea"
|
||
[(ngModel)]="rejectionReason"
|
||
rows="4"
|
||
placeholder="e.g. Amount does not match the withdrawal slip; please verify and resubmit."
|
||
></textarea>
|
||
</div>
|
||
<div class="te-confirm-actions">
|
||
<button class="te-btn" type="button" (click)="cancelRejectReview()">Cancel</button>
|
||
<button class="te-btn te-btn--danger" type="button" [disabled]="saving" (click)="confirmRejectReview()">
|
||
{{ saving ? 'Rejecting…' : 'Confirm Reject' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── CONFIRMATION DIALOG ──────────────────────────────────────────────── -->
|
||
@if (showConfirmDialog && confirmData) {
|
||
<div class="te-confirm-overlay" (click)="showConfirmDialog = false">
|
||
<div class="te-confirm-box" (click)="$event.stopPropagation()">
|
||
<div
|
||
class="te-confirm-header"
|
||
[class.te-confirm-header--deposit]="confirmData.type === 'DEPOSIT'"
|
||
[class.te-confirm-header--withdrawal]="confirmData.type === 'WITHDRAWAL'"
|
||
[class.te-confirm-header--disbursement]="confirmData.isDisbursement || confirmData.isDepositRequest"
|
||
>
|
||
<span class="te-confirm-icon">{{ confirmData.type === 'WITHDRAWAL' ? '⬆' : '⬇' }}</span>
|
||
<span>Confirm {{ confirmData.isDisbursement ? 'CASH DISBURSEMENT' : confirmData.type }}</span>
|
||
</div>
|
||
<div class="te-confirm-body">
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Account</span
|
||
><span class="te-confirm-val te-mono">{{ confirmData.accountNo }}</span>
|
||
</div>
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Customer</span><span class="te-confirm-val">{{ confirmData.clientName }}</span>
|
||
</div>
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Payment Type</span
|
||
><span class="te-confirm-val">{{ confirmData.paymentType }}</span>
|
||
</div>
|
||
@if (confirmData.chequeNo) {
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Cheque No.</span
|
||
><span class="te-confirm-val te-mono">{{ confirmData.chequeNo }}</span>
|
||
</div>
|
||
}
|
||
@if (confirmData.tokNo) {
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Token No.</span
|
||
><span class="te-confirm-val te-mono"
|
||
><strong>{{ confirmData.tokNo }}</strong></span
|
||
>
|
||
</div>
|
||
}
|
||
<div class="te-confirm-divider"></div>
|
||
|
||
@if (confirmData.isDisbursement) {
|
||
<!-- Token-based disbursement: balance was already deducted at manager approval -->
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Current Balance</span>
|
||
<span class="te-confirm-val"
|
||
>{{ confirmData.symbol }}{{ confirmData.currentBalance | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
<div class="te-confirm-row te-confirm-row--amount">
|
||
<span class="te-confirm-label">Disburse Amount</span>
|
||
<span class="te-confirm-val te-cr">{{ confirmData.symbol }}{{ confirmData.amount | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-confirm-row te-confirm-disbursed-note">
|
||
<span class="te-confirm-disbursed-badge"
|
||
>Balance already deducted at manager approval — no further deduction</span
|
||
>
|
||
</div>
|
||
} @else if (confirmData.isDepositRequest) {
|
||
<!-- Maker-checker deposit: balance credits only after manager approval -->
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Current Balance</span>
|
||
<span class="te-confirm-val"
|
||
>{{ confirmData.symbol }}{{ confirmData.currentBalance | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
@if (confirmData.isDdCashGl) {
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">DD Face Value</span
|
||
><span class="te-confirm-val"
|
||
>{{ confirmData.symbol }}{{ confirmData.ddFaceValue | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Charges</span
|
||
><span class="te-confirm-val">{{ confirmData.symbol }}{{ confirmData.ddCharges | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">GST ({{ confirmData.ddGstPercentage }}%)</span
|
||
><span class="te-confirm-val"
|
||
>{{ confirmData.symbol }}{{ confirmData.ddGstAmount | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
}
|
||
<div class="te-confirm-row te-confirm-row--amount">
|
||
<span class="te-confirm-label">{{ confirmData.isDdCashGl ? 'Total To Collect' : 'Deposit Amount' }}</span>
|
||
<span class="te-confirm-val te-cr"
|
||
>+{{ confirmData.symbol }}{{ confirmData.amount | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
<div class="te-confirm-row te-confirm-disbursed-note">
|
||
<span class="te-confirm-disbursed-badge"
|
||
>Sent for manager approval — balance will be credited only after approval</span
|
||
>
|
||
</div>
|
||
} @else {
|
||
<div class="te-confirm-row">
|
||
<span class="te-confirm-label">Current Balance</span
|
||
><span class="te-confirm-val"
|
||
>{{ confirmData.symbol }}{{ confirmData.currentBalance | number: '1.2-2' }}</span
|
||
>
|
||
</div>
|
||
<div class="te-confirm-row te-confirm-row--amount">
|
||
<span class="te-confirm-label">{{ confirmData.type === 'DEPOSIT' ? 'Deposit' : 'Withdrawal' }} Amount</span>
|
||
<span
|
||
class="te-confirm-val"
|
||
[class.te-cr]="confirmData.type === 'DEPOSIT'"
|
||
[class.te-dr]="confirmData.type === 'WITHDRAWAL'"
|
||
>
|
||
{{ confirmData.type === 'DEPOSIT' ? '+' : '−' }}{{ confirmData.symbol
|
||
}}{{ confirmData.amount | number: '1.2-2' }}
|
||
</span>
|
||
</div>
|
||
<div class="te-confirm-row te-confirm-row--after">
|
||
<span class="te-confirm-label">Balance After</span>
|
||
<span
|
||
class="te-confirm-val"
|
||
[class.te-cr]="confirmData.balanceAfter >= 0"
|
||
[class.te-dr]="confirmData.balanceAfter < 0"
|
||
>
|
||
<strong>{{ confirmData.symbol }}{{ confirmData.balanceAfter | number: '1.2-2' }}</strong>
|
||
</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
<div class="te-confirm-actions">
|
||
<button class="te-btn" type="button" (click)="showConfirmDialog = false">Cancel</button>
|
||
<button class="te-btn te-btn--primary" type="button" [disabled]="saving" (click)="confirmSubmit()">
|
||
{{
|
||
saving
|
||
? 'Posting…'
|
||
: confirmData.isDisbursement
|
||
? 'Confirm DISBURSEMENT'
|
||
: confirmData.isDepositRequest
|
||
? 'Submit for Approval'
|
||
: 'Confirm ' + confirmData.type
|
||
}}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ── Custom Alert / Confirm Modal ──────────────────────────────────────── -->
|
||
@if (alertModal) {
|
||
<div class="te-alert-overlay" (click)="closeAlert()">
|
||
<div class="te-alert-box te-alert-box--{{ alertModal.type }}" (click)="$event.stopPropagation()">
|
||
<!-- Left sidebar -->
|
||
<div class="te-alert-box__sidebar">
|
||
<div class="te-alert-box__sidebar-icon">
|
||
@if (alertModal.type === 'error') {
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<circle cx="12" cy="12" r="10" />
|
||
<line x1="15" y1="9" x2="9" y2="15" />
|
||
<line x1="9" y1="9" x2="15" y2="15" />
|
||
</svg>
|
||
}
|
||
@if (alertModal.type === 'warning') {
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||
<line x1="12" y1="9" x2="12" y2="13" />
|
||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||
</svg>
|
||
}
|
||
@if (alertModal.type === 'info') {
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<circle cx="12" cy="12" r="10" />
|
||
<line x1="12" y1="8" x2="12" y2="12" />
|
||
<line x1="12" y1="16" x2="12.01" y2="16" />
|
||
</svg>
|
||
}
|
||
@if (alertModal.type === 'success') {
|
||
<svg
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="2.5"
|
||
stroke-linecap="round"
|
||
stroke-linejoin="round"
|
||
>
|
||
<circle cx="12" cy="12" r="10" />
|
||
<polyline points="9 12 11 14 15 10" />
|
||
</svg>
|
||
}
|
||
</div>
|
||
<span class="te-alert-box__sidebar-type">
|
||
{{
|
||
alertModal.type === 'error'
|
||
? 'ERROR'
|
||
: alertModal.type === 'warning'
|
||
? 'WARN'
|
||
: alertModal.type === 'success'
|
||
? 'OK'
|
||
: 'INFO'
|
||
}}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- Right content -->
|
||
<div class="te-alert-box__content">
|
||
<div class="te-alert-box__content-head">
|
||
<h3>{{ alertModal.title }}</h3>
|
||
<button class="te-alert-box__close" (click)="closeAlert()">
|
||
<svg
|
||
width="14"
|
||
height="14"
|
||
viewBox="0 0 24 24"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
stroke-width="2.5"
|
||
stroke-linecap="round"
|
||
>
|
||
<line x1="18" y1="6" x2="6" y2="18" />
|
||
<line x1="6" y1="6" x2="18" y2="18" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="te-alert-box__lines">
|
||
@for (line of alertModal.lines; track $index) {
|
||
@if ($index === 0) {
|
||
<p class="te-alert-box__desc">{{ line }}</p>
|
||
} @else if (line.startsWith('⚠')) {
|
||
<div class="te-alert-box__callout">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
|
||
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
|
||
<line x1="12" y1="9" x2="12" y2="13" />
|
||
<line x1="12" y1="17" x2="12.01" y2="17" />
|
||
</svg>
|
||
<span>{{ line.slice(1).trim() }}</span>
|
||
</div>
|
||
} @else if (line.includes(':') && $index < alertModal.lines.length - 1) {
|
||
<div class="te-alert-box__kv">
|
||
<span class="te-alert-box__kv-k">{{ line.split(':')[0].trim() }}</span>
|
||
<span class="te-alert-box__kv-v">{{ line.split(':').slice(1).join(':').trim() }}</span>
|
||
</div>
|
||
} @else {
|
||
<p class="te-alert-box__foot">{{ line }}</p>
|
||
}
|
||
}
|
||
</div>
|
||
|
||
<div class="te-alert-box__actions">
|
||
@if (alertModal.onConfirm) {
|
||
<button class="te-alert-btn te-alert-btn--ghost" type="button" (click)="closeAlert()">Cancel</button>
|
||
<button
|
||
class="te-alert-btn te-alert-btn--solid te-alert-btn--{{ alertModal.type }}"
|
||
type="button"
|
||
(click)="confirmAlert()"
|
||
>
|
||
{{ alertModal.confirmLabel ?? 'Confirm' }}
|
||
</button>
|
||
} @else {
|
||
<button
|
||
class="te-alert-btn te-alert-btn--solid te-alert-btn--{{ alertModal.type }}"
|
||
type="button"
|
||
(click)="closeAlert()"
|
||
>
|
||
{{ alertModal.type === 'success' ? 'Done' : 'OK, Got It' }}
|
||
</button>
|
||
}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|