Files
cbsnexora/src/app/cashier/transaction-entry/transaction-entry.component.html
T

2476 lines
118 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
Copyright since 2025 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="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 &amp; 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 &gt; 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 &amp; 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 &amp; 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>
}