- Cash Exchange Entry: customer details with PAN (mandatory above Rs 50,000), cashier card, denomination-wise validation against the master and real-time impact preview. - Inter-Cashier Transfer: sender fixed to the logged-in cashier, both sides' details and denomination holdings shown, blocked until both are allocated cash. - Reconciliation, exchange and inter-cashier operate on the logged-in cashier only. - New Branch Cash Position screen (vault + cashier drawers, denomination-wise). - Customer search dialog shows the KYC PAN. - Cashier menu cleanup: Excess/Closing report and Exchange Detail removed, nav compacted to one row. - Reverse actions removed from cash transfer, settlement, exchange and inter-cashier. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
231 lines
8.8 KiB
HTML
231 lines
8.8 KiB
HTML
<!--
|
||
Copyright since 2025 Mifos Initiative
|
||
|
||
This Source Code Form is subject to the terms of the Mozilla Public
|
||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
-->
|
||
|
||
<div class="pg">
|
||
<header class="pg-head">
|
||
<div>
|
||
<div class="pg-eyebrow">CASHIER · INTER-CASHIER TRANSFER</div>
|
||
<h1 class="pg-title">Inter-Cashier Transfer</h1>
|
||
<p class="pg-sub">Hand over denomination-wise cash from your drawer to another cashier at your counter.</p>
|
||
</div>
|
||
<button type="button" class="pg-btn pg-btn--ghost" (click)="clear()"><mat-icon>undo</mat-icon> Clear</button>
|
||
</header>
|
||
|
||
@if (ownCashierMissing) {
|
||
<div class="ict-alert">
|
||
<mat-icon>person_off</mat-icon>
|
||
Your login is not linked to a cashier. An inter-cashier transfer is made by the cashier handing the cash over, so
|
||
log in as that cashier.
|
||
</div>
|
||
}
|
||
|
||
<div class="pg-toolbar pg-toolbar--mat">
|
||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||
<mat-label>Teller</mat-label>
|
||
<input matInput type="text" [value]="tellerName || '—'" readonly />
|
||
</mat-form-field>
|
||
|
||
<!-- The sender is always the cashier who is logged in; nothing to pick. -->
|
||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||
<mat-label>From (You)</mat-label>
|
||
<input matInput type="text" [value]="fromParty || '—'" readonly />
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||
<mat-label>To (Cashier)</mat-label>
|
||
<mat-select [value]="toCashierId" (selectionChange)="onToSelect($event.value)" [disabled]="!fromCashierId">
|
||
@for (c of availableTo; track c.id) {
|
||
<mat-option [value]="c.id">{{ c.label }}</mat-option>
|
||
}
|
||
@if (fromCashierId && !loadingCashiers && availableTo.length === 0) {
|
||
<mat-option [value]="null" disabled>No other cashiers</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||
<mat-label>Date</mat-label>
|
||
<input matInput type="date" [value]="transferDate" (change)="onDateChange($any($event.target).value)" />
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="pg-note-field">
|
||
<mat-label>Note</mat-label>
|
||
<input matInput type="text" [value]="note" (input)="note = $any($event.target).value" placeholder="Optional" />
|
||
</mat-form-field>
|
||
</div>
|
||
|
||
<!-- Both sides of the transfer: who they are, whether the vault has allocated them cash today,
|
||
and what they hold now. -->
|
||
@if (fromCashierId || toCashierId) {
|
||
<div class="ict-parties">
|
||
<ng-container
|
||
*ngTemplateOutlet="
|
||
partyCard;
|
||
context: { title: 'From (Sending Cashier)', s: fromSummary, loading: loadingFrom, picked: !!fromCashierId }
|
||
"
|
||
></ng-container>
|
||
<mat-icon class="ict-parties__arrow">arrow_forward</mat-icon>
|
||
<ng-container
|
||
*ngTemplateOutlet="
|
||
partyCard;
|
||
context: { title: 'To (Receiving Cashier)', s: toSummary, loading: loadingTo, picked: !!toCashierId }
|
||
"
|
||
></ng-container>
|
||
</div>
|
||
}
|
||
|
||
<ng-template #partyCard let-title="title" let-s="s" let-loading="loading" let-picked="picked">
|
||
<div class="ict-party" [class.ict-party--blocked]="s && !s.cashAllocated">
|
||
<div class="ict-party__title">{{ title }}</div>
|
||
@if (!picked) {
|
||
<div class="ict-party__empty">Select a cashier</div>
|
||
} @else if (loading) {
|
||
<div class="ict-party__empty">Loading…</div>
|
||
} @else if (!s) {
|
||
<div class="ict-party__empty">Could not load this cashier's details.</div>
|
||
} @else {
|
||
<div class="ict-party__name">{{ s.cashierName || '—' }}</div>
|
||
<div class="ict-party__meta">
|
||
<span><mat-icon>point_of_sale</mat-icon> {{ s.tellerName || '—' }}</span>
|
||
<span><mat-icon>account_balance</mat-icon> {{ s.officeName || '—' }}</span>
|
||
</div>
|
||
<div class="ict-party__figures">
|
||
<div>
|
||
<div class="ict-party__label">Allocated today</div>
|
||
<div class="ict-party__value">₹{{ s.allocatedAmount | number: '1.2-2' }}</div>
|
||
</div>
|
||
<div>
|
||
<div class="ict-party__label">Cash in hand</div>
|
||
<div class="ict-party__value">₹{{ s.totalAmount | number: '1.2-2' }}</div>
|
||
</div>
|
||
</div>
|
||
@if (!s.cashAllocated) {
|
||
<div class="ict-party__warn">
|
||
<mat-icon>block</mat-icon> No cash allocated from the vault for this date — inter-cashier transfer is not
|
||
allowed.
|
||
</div>
|
||
}
|
||
}
|
||
</div>
|
||
</ng-template>
|
||
|
||
@if (fromSummary && toSummary && !bothAllocated) {
|
||
<div class="ict-alert">
|
||
<mat-icon>block</mat-icon>
|
||
Both cashiers must first be allocated cash from the vault (Cash Transfer) for {{ transferDate }} before an
|
||
inter-cashier transfer.
|
||
</div>
|
||
}
|
||
@if (anyExceedsHeld) {
|
||
<div class="ict-alert">
|
||
<mat-icon>error_outline</mat-icon>
|
||
The sending cashier does not hold enough of the highlighted denominations.
|
||
</div>
|
||
}
|
||
|
||
<div class="pg-card">
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</th>
|
||
<th class="num">From holds</th>
|
||
<th style="text-align: center">Count</th>
|
||
<th class="num">Amount</th>
|
||
<th class="num">To holds</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@if (notes.length) {
|
||
<tr class="pg-section">
|
||
<td colspan="5">Bank Notes</td>
|
||
</tr>
|
||
}
|
||
@for (d of notes; track d.id) {
|
||
<tr [class.ict-row--over]="exceedsHeld(d)">
|
||
<td>
|
||
<span class="pg-chip">{{ d.label }}</span>
|
||
</td>
|
||
<td class="num" [class.ict-held--over]="exceedsHeld(d)">
|
||
<ng-container
|
||
*ngTemplateOutlet="heldCell; context: { n: heldBy(fromSummary, d), delta: -countFor(d) }"
|
||
></ng-container>
|
||
</td>
|
||
<td style="display: flex; justify-content: center">
|
||
<div class="pg-step">
|
||
<button type="button" (click)="bump(d, -1)" [disabled]="countFor(d) === 0">−</button>
|
||
<input type="number" min="0" [value]="countFor(d)" (input)="onChange(d, $any($event.target).value)" />
|
||
<button type="button" (click)="bump(d, 1)">+</button>
|
||
</div>
|
||
</td>
|
||
<td class="num">₹{{ subtotal(d) | number: '1.2-2' }}</td>
|
||
<td class="num">
|
||
<ng-container
|
||
*ngTemplateOutlet="heldCell; context: { n: heldBy(toSummary, d), delta: countFor(d) }"
|
||
></ng-container>
|
||
</td>
|
||
</tr>
|
||
}
|
||
@if (coins.length) {
|
||
<tr class="pg-section">
|
||
<td colspan="5">Coins</td>
|
||
</tr>
|
||
}
|
||
@for (d of coins; track d.id) {
|
||
<tr [class.ict-row--over]="exceedsHeld(d)">
|
||
<td>
|
||
<span class="pg-chip pg-chip--coin">{{ d.label }}</span>
|
||
</td>
|
||
<td class="num" [class.ict-held--over]="exceedsHeld(d)">
|
||
<ng-container
|
||
*ngTemplateOutlet="heldCell; context: { n: heldBy(fromSummary, d), delta: -countFor(d) }"
|
||
></ng-container>
|
||
</td>
|
||
<td style="display: flex; justify-content: center">
|
||
<div class="pg-step">
|
||
<button type="button" (click)="bump(d, -1)" [disabled]="countFor(d) === 0">−</button>
|
||
<input type="number" min="0" [value]="countFor(d)" (input)="onChange(d, $any($event.target).value)" />
|
||
<button type="button" (click)="bump(d, 1)">+</button>
|
||
</div>
|
||
</td>
|
||
<td class="num">₹{{ subtotal(d) | number: '1.2-2' }}</td>
|
||
<td class="num">
|
||
<ng-container
|
||
*ngTemplateOutlet="heldCell; context: { n: heldBy(toSummary, d), delta: countFor(d) }"
|
||
></ng-container>
|
||
</td>
|
||
</tr>
|
||
}
|
||
@if (denoms.length === 0) {
|
||
<tr>
|
||
<td colspan="5" class="pg-empty">No active denominations. Add some in Cash Denomination.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- A held count, with what it becomes once this transfer is recorded. -->
|
||
<ng-template #heldCell let-n="n" let-delta="delta">
|
||
@if (n === null) {
|
||
—
|
||
} @else {
|
||
{{ n }}
|
||
@if (delta !== 0) {
|
||
<span class="ict-after">→ {{ n + delta }}</span>
|
||
}
|
||
}
|
||
</ng-template>
|
||
|
||
<div class="pg-total-bar">
|
||
Total to transfer: <strong>₹{{ total | number: '1.2-2' }}</strong>
|
||
<button type="button" class="pg-btn pg-btn--primary" [disabled]="!valid || saving" (click)="submit()">
|
||
<mat-icon>swap_horiz</mat-icon> Record Transfer
|
||
</button>
|
||
</div>
|
||
</div>
|