feat(cashier): complete maker-checker withdrawal disbursement flow
- Clerk manually enters token no with today-date unique validation (backend enforced) - Cashier token lookup: auto-fill amount/account, lock amount field, status-specific errors - Confirm dialog shows CASH DISBURSEMENT (blue) with no balance re-deduction warning - Disbursement denomination saved to m_savings_txn_denomination for vault position update - Block direct cashier withdrawal without approved maker-checker token - DISBURSED status shown in My Requests and Checker inbox views with cashier/time info - Remove getNextToken() service method and m_daily_token_counter table (0326 migration) - Race condition protection: UPDATE WHERE status=APPROVED prevents double disbursement Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
de420bb8ab
commit
61cb09f352
@@ -7,6 +7,22 @@
|
||||
-->
|
||||
|
||||
<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">
|
||||
@@ -186,17 +202,27 @@
|
||||
<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="Transaction Amount">AMOUNT</label>
|
||||
<div class="ws-amt-wrap" [class.ws-amt-wrap--cash]="isCashPayment && selectedRow === row">
|
||||
<label class="ws-f__lbl" title="Transaction Amount">
|
||||
AMOUNT
|
||||
@if (!isClerk && activeWithdrawalRequest) {
|
||||
<span class="ws-amt-locked-badge" title="Amount locked — approved withdrawal">🔒</span>
|
||||
}
|
||||
</label>
|
||||
<div
|
||||
class="ws-amt-wrap"
|
||||
[class.ws-amt-wrap--cash]="isCashPayment && selectedRow === row"
|
||||
[class.ws-amt-wrap--locked]="!isClerk && !!activeWithdrawalRequest"
|
||||
>
|
||||
<span class="ws-amt-prefix">₹</span>
|
||||
<input
|
||||
class="ws-f__ctl ws-amt-input"
|
||||
type="number"
|
||||
[value]="isCashPayment && selectedRow === row ? denominationTotal || '' : (row.amount ?? '')"
|
||||
[disabled]="isCashPayment && selectedRow === row"
|
||||
[value]="row.amount ?? ''"
|
||||
(input)="row.amount = +$any($event.target).value"
|
||||
(click)="$event.stopPropagation()"
|
||||
placeholder="0.00"
|
||||
[readonly]="!isClerk && !!activeWithdrawalRequest"
|
||||
[class.ws-amt-input--locked]="!isClerk && !!activeWithdrawalRequest"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -220,7 +246,15 @@
|
||||
</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" (click)="$event.stopPropagation()" />
|
||||
<input
|
||||
class="ws-f__ctl"
|
||||
[(ngModel)]="row.tokNo"
|
||||
[readonly]="!isClerk && !!activeWithdrawalRequest"
|
||||
[class.ws-f__ctl--readonly]="!isClerk && !!activeWithdrawalRequest"
|
||||
placeholder="Enter token"
|
||||
(blur)="onTokNoBlur(row)"
|
||||
(click)="$event.stopPropagation()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -249,6 +283,45 @@
|
||||
</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>DISBURSED</strong>.
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="ws-withdrawal-banner__clear" (click)="activeWithdrawalRequest = null" title="Unlink">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ── STATUS ────────────────────────────────────────────────────────── -->
|
||||
@if (loadingAccount) {
|
||||
<div class="te-loading">
|
||||
@@ -1057,177 +1130,283 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<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">
|
||||
<option value="~">~</option>
|
||||
<option value="Withdrawal Slip">Withdrawal Slip</option>
|
||||
<option value="Cheque">Cheque</option>
|
||||
<option value="Disbursement">Disbursement</option>
|
||||
<option value="Enter Bank Transfer">Enter Bank Transfer</option>
|
||||
<option value="DD">DD (Demand Draft)</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">
|
||||
<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>
|
||||
<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"
|
||||
[class.ws-f__ctl--req]="!row.paymentTypeId"
|
||||
>
|
||||
<option [ngValue]="null">— Select —</option>
|
||||
@for (pt of paymentTypes; 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>
|
||||
<select
|
||||
class="ws-f__ctl"
|
||||
[(ngModel)]="row.acTyp"
|
||||
(change)="row.acTyp === 'GL' && (row.dbCr = 'Cr')"
|
||||
>
|
||||
@for (a of acTypOptions; track a) {
|
||||
<option [value]="a">{{ a }}</option>
|
||||
}
|
||||
</select>
|
||||
<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">
|
||||
<option value="~">~</option>
|
||||
<option value="Withdrawal Slip">Withdrawal Slip</option>
|
||||
<option value="Cheque">Cheque</option>
|
||||
<option value="Disbursement">Disbursement</option>
|
||||
<option value="Enter Bank Transfer">Enter Bank Transfer</option>
|
||||
<option value="DD">DD (Demand Draft)</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">
|
||||
<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>
|
||||
<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"
|
||||
[class.ws-f__ctl--req]="!row.paymentTypeId"
|
||||
>
|
||||
<option [ngValue]="null">— Select —</option>
|
||||
@for (pt of paymentTypes; 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>
|
||||
<select
|
||||
class="ws-f__ctl"
|
||||
[(ngModel)]="row.acTyp"
|
||||
(change)="row.acTyp === 'GL' && (row.dbCr = 'Cr')"
|
||||
>
|
||||
@for (a of acTypOptions; track a) {
|
||||
<option [value]="a">{{ a }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</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="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'"
|
||||
(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 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="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'"
|
||||
(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>
|
||||
<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">AMOUNT</label>
|
||||
<div class="ws-amt-wrap" [class.ws-amt-wrap--cash]="isCashPayment && selectedRow === row">
|
||||
<span class="ws-amt-prefix">₹</span>
|
||||
<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">
|
||||
AMOUNT
|
||||
@if (!isClerk && activeWithdrawalRequest) {
|
||||
<span class="ws-amt-locked-badge" title="Amount locked — approved withdrawal">🔒</span>
|
||||
}
|
||||
</label>
|
||||
<div
|
||||
class="ws-amt-wrap"
|
||||
[class.ws-amt-wrap--cash]="isCashPayment && selectedRow === row"
|
||||
[class.ws-amt-wrap--locked]="!isClerk && !!activeWithdrawalRequest"
|
||||
>
|
||||
<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"
|
||||
(click)="$event.stopPropagation()"
|
||||
placeholder="0.00"
|
||||
[readonly]="!isClerk && !!activeWithdrawalRequest"
|
||||
[class.ws-amt-input--locked]="!isClerk && !!activeWithdrawalRequest"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<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 ws-amt-input"
|
||||
type="number"
|
||||
[value]="
|
||||
isCashPayment && selectedRow === row ? denominationTotal || '' : (row.amount ?? '')
|
||||
"
|
||||
[disabled]="isCashPayment && selectedRow === row"
|
||||
(input)="row.amount = +$any($event.target).value"
|
||||
placeholder="0.00"
|
||||
class="ws-f__ctl"
|
||||
[(ngModel)]="row.tokNo"
|
||||
[readonly]="!isClerk && !!activeWithdrawalRequest"
|
||||
[class.ws-f__ctl--readonly]="!isClerk && !!activeWithdrawalRequest"
|
||||
placeholder="Enter token"
|
||||
(blur)="onTokNoBlur(row)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<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" />
|
||||
</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" placeholder="Narration / remarks" />
|
||||
<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" placeholder="Narration / remarks" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons footer -->
|
||||
<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 (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>
|
||||
<div class="ws-footer__actions">
|
||||
<button class="ws-btn ws-btn--post" type="button" (click)="onSave()" [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 ? 'Posting…' : 'Save' }}
|
||||
</button>
|
||||
} @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>
|
||||
</div>
|
||||
} @else {
|
||||
<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>
|
||||
</div>
|
||||
<div class="ws-footer__actions">
|
||||
<button class="ws-btn ws-btn--post" type="button" (click)="onSave()" [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 ? 'Posting…' : 'Save' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</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">
|
||||
@@ -1236,9 +1415,10 @@
|
||||
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"
|
||||
>
|
||||
<span class="te-confirm-icon">{{ confirmData.type === 'DEPOSIT' ? '⬇' : '⬆' }}</span>
|
||||
<span>Confirm {{ confirmData.type }}</span>
|
||||
<span>Confirm {{ confirmData.isDisbursement ? 'CASH DISBURSEMENT' : confirmData.type }}</span>
|
||||
</div>
|
||||
<div class="te-confirm-body">
|
||||
<div class="te-confirm-row">
|
||||
@@ -1258,39 +1438,69 @@
|
||||
><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>
|
||||
<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>
|
||||
|
||||
@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 {
|
||||
<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…' : 'Confirm ' + confirmData.type }}
|
||||
{{
|
||||
saving ? 'Posting…' : confirmData.isDisbursement ? 'Confirm DISBURSEMENT' : 'Confirm ' + confirmData.type
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user