Files
cbsnexora/src/app/cashier/cash-transfer/cash-transfer.component.html
T
niteshandClaude Sonnet 5.5 7ee230e58a feat(cashier): maker-checker on cash transfer, real branches, compact Approval Center
- Cash Transfer now raises a request ("Send for approval") instead of posting. A My Requests button in the
  page header opens the caller's requests with their status, and a Cash Transfers approvals page (review
  popup with the full denomination breakdown, approve, reject with a reason) lets a different manager release
  the cash.
- Transaction Entry's Branch dropdown lists the installation's real offices instead of a fixed list. A login
  that cannot read offices is offered only its own branch (read-only), so no "Unauthorized Request" popup.
- Approval Center header and tab bar made compact so more tabs fit on a 100% window.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-15 12:27:39 +05:30

219 lines
8.3 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="pg">
<header class="pg-head">
<div>
<div class="pg-eyebrow">CASHIER · CASH TRANSFER</div>
<h1 class="pg-title">Cash Transfer</h1>
<p class="pg-sub">Move cash denomination-wise from one party to another (e.g. Vault → Cashier for opening).</p>
</div>
<div class="ct-head-actions">
<button type="button" class="pg-btn pg-btn--ghost" (click)="openRequests()">
<mat-icon>pending_actions</mat-icon> My Requests
@if (pendingCount) {
<span class="ct-badge">{{ pendingCount }}</span>
}
</button>
<button type="button" class="pg-btn pg-btn--ghost" (click)="clear()"><mat-icon>undo</mat-icon> Clear</button>
</div>
</header>
<div class="pg-toolbar pg-toolbar--mat">
@if (tellers.length === 1) {
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>From (Teller / Head)</mat-label>
<input matInput type="text" [value]="fromParty" readonly />
</mat-form-field>
} @else {
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>From (Teller / Head)</mat-label>
<mat-select [value]="fromTellerId" (selectionChange)="onTellerSelect($event.value)">
@for (t of tellers; track t.id) {
<mat-option [value]="t.id">{{ t.name }}</mat-option>
}
@if (tellers.length === 0) {
<mat-option [value]="null" disabled>No tellers configured</mat-option>
}
</mat-select>
</mat-form-field>
}
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>To (Cashier)</mat-label>
<mat-select [value]="toCashierId" (selectionChange)="onCashierSelect($event.value)" [disabled]="!fromTellerId">
@for (c of cashiers; track c.id) {
<mat-option [value]="c.id">{{ c.label }}</mat-option>
}
@if (fromTellerId && cashiers.length === 0 && !loadingCashiers) {
<mat-option [value]="null" disabled>No cashiers for this teller</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>
<!-- What the vault actually has, before anyone starts counting a handout it can't cover. -->
@if (fromTellerId && !loadingVault) {
@if (vaultHasBreakdown) {
<div class="ct-vault-banner">
<mat-icon>account_balance</mat-icon>
Vault available: <strong>₹{{ vaultTotalAvailable | number: '1.2-2' }}</strong>
</div>
} @else {
<div class="ct-vault-banner ct-vault-banner--warn">
<mat-icon>warning</mat-icon>
No denomination breakdown recorded for this vault — per-note availability can't be checked, only the total
opening balance.
</div>
}
}
<div class="pg-card">
<table class="pg-table">
<thead>
<tr>
<th>Denomination</th>
<th class="num">Available</th>
<th style="text-align: center">Count</th>
<th class="num">Amount</th>
</tr>
</thead>
<tbody>
@if (notes.length) {
<tr class="pg-section">
<td colspan="4">Bank Notes</td>
</tr>
}
@for (d of notes; track d.id) {
<tr [class.ct-row--over]="exceedsAvailable(d)">
<td>
<span class="pg-chip">{{ d.label }}</span>
</td>
<td class="num" [class.ct-available--over]="exceedsAvailable(d)">
{{ vaultHasBreakdown ? availableFor(d) : '—' }}
</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>
</tr>
}
@if (coins.length) {
<tr class="pg-section">
<td colspan="4">Coins</td>
</tr>
}
@for (d of coins; track d.id) {
<tr [class.ct-row--over]="exceedsAvailable(d)">
<td>
<span class="pg-chip pg-chip--coin">{{ d.label }}</span>
</td>
<td class="num" [class.ct-available--over]="exceedsAvailable(d)">
{{ vaultHasBreakdown ? availableFor(d) : '—' }}
</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>
</tr>
}
@if (denoms.length === 0) {
<tr>
<td colspan="4" class="pg-empty">No active denominations. Add some in Cash Denomination.</td>
</tr>
}
</tbody>
</table>
</div>
@if (anyDenomExceedsAvailable) {
<div class="ct-limit-warn">
One or more notes/coins above are counted higher than the vault currently holds — reduce them before recording the
transfer.
</div>
} @else if (exceedsVaultTotal) {
<div class="ct-limit-warn">
Total exceeds what the vault has available (₹{{ vaultTotalAvailable | number: '1.2-2' }}).
</div>
}
<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>send</mat-icon> Send for approval
</button>
</div>
</div>
<!-- Popup: this person's own cash transfer requests, and where each stands. The cash moves only once a manager approves. -->
<ng-template #requestsDialog>
<div class="ct-dlg">
<header class="ct-dlg__head">
<mat-icon class="ct-dlg__ico">pending_actions</mat-icon>
<div class="ct-dlg__titles">
<div class="ct-dlg__title">My transfer requests</div>
<div class="ct-dlg__sub">Cash leaves the vault only after a manager approves</div>
</div>
<button type="button" class="ct-dlg__x" aria-label="Close" (click)="closeRequests()">
<mat-icon>close</mat-icon>
</button>
</header>
<div class="ct-dlg__body">
@if (myRequests.length) {
<table class="pg-table">
<thead>
<tr>
<th>Date</th>
<th>To</th>
<th class="num">Amount</th>
<th>Status</th>
<th>Approved by</th>
</tr>
</thead>
<tbody>
@for (r of myRequests; track r.id) {
<tr>
<td>{{ r.transfer_date || '—' }}</td>
<td>{{ r.to_party || '—' }}</td>
<td class="num">₹{{ r.total_amount | number: '1.2-2' }}</td>
<td>
<span class="ct-status" [class]="'ct-status ct-status--' + r.status">{{ r.status }}</span>
@if (r.rejection_reason) {
<div class="ct-requests__note">{{ r.rejection_reason }}</div>
}
</td>
<td>{{ r.checker_name || '—' }}</td>
</tr>
}
</tbody>
</table>
} @else {
<div class="pg-empty">You have not raised any cash transfer request yet.</div>
}
</div>
</div>
</ng-template>