Files
cbsnexora/src/app/cashier/cash-settlement/cash-settlement.component.html
T
2026-06-24 16:32:37 +05:30

414 lines
17 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 SETTLEMENT</div>
<h1 class="pg-title">Cash Settlement</h1>
<p class="pg-sub">
End-of-day settlement — verify your denomination count against system expected balance and hand cash to vault.
</p>
</div>
</header>
<!-- ── LOADING ── -->
@if (pageState === 'loading') {
<div class="settle-state">
<mat-icon class="settle-state__icon settle-state__icon--spin">hourglass_empty</mat-icon>
<p>Loading your cashier position…</p>
</div>
}
<!-- ── SUPERVISOR MODE — Select cashier to settle ── -->
@if (pageState === 'no-cashier') {
<div class="settle-supervisor-card">
<div class="settle-supervisor-header">
<mat-icon>manage_accounts</mat-icon>
<div>
<strong>Supervisor / Admin Settlement</strong>
<span
>Your account is not linked to a cashier drawer. Select a teller and cashier below to process their
end-of-day settlement.</span
>
</div>
</div>
<div class="pg-toolbar pg-toolbar--mat" style="margin-top: 16px">
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>Settlement Date</mat-label>
<input matInput type="date" [value]="settlementDate" (change)="settlementDate = $any($event.target).value" />
</mat-form-field>
@if (tellers.length === 1) {
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>Teller</mat-label>
<input matInput type="text" [value]="tellers[0].name" readonly />
</mat-form-field>
} @else {
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>Teller</mat-label>
<mat-select [value]="supervisorTellerId" (selectionChange)="onSupervisorTellerSelect($event.value)">
@for (t of tellers; track t.id) {
<mat-option [value]="t.id">{{ t.name }}</mat-option>
}
</mat-select>
</mat-form-field>
}
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>Cashier</mat-label>
<mat-select
[value]="myCashierId"
(selectionChange)="onSupervisorCashierSelect($event.value)"
[disabled]="!supervisorTellerId || loadingCashiers"
>
@for (c of supervisorCashiers; track c.id) {
<mat-option [value]="c.id">{{ c.label }}</mat-option>
}
@if (supervisorTellerId && supervisorCashiers.length === 0 && !loadingCashiers) {
<mat-option [value]="null" disabled>No cashiers found</mat-option>
}
</mat-select>
</mat-form-field>
</div>
@if (supervisorChecking) {
<div class="pg-empty">Checking cashier status…</div>
}
</div>
}
<!-- ── RECONCILIATION PENDING ── -->
@if (pageState === 'needs-reconciliation') {
<div class="settle-alert settle-alert--warn">
<mat-icon>assignment_late</mat-icon>
<div>
<strong>Reconciliation required before settlement</strong>
<p>
{{ myCashierName ? myCashierName + ' has' : 'You have' }} not completed today's cash reconciliation.
Settlement cannot proceed until reconciliation is done and any variance is documented.
</p>
<div style="display: flex; gap: 10px; margin-top: 10px; flex-wrap: wrap">
<button type="button" class="pg-btn pg-btn--primary" (click)="goToReconciliation()">
<mat-icon>assignment</mat-icon> Go to Reconciliation
</button>
@if (isSupervisor) {
<button type="button" class="pg-btn" (click)="pageState = 'no-cashier'">
<mat-icon>arrow_back</mat-icon> Select Different Cashier
</button>
}
</div>
</div>
</div>
}
<!-- ── ALREADY SETTLED ── -->
@if (pageState === 'already-settled') {
<div class="settle-alert settle-alert--ok">
<mat-icon>check_circle</mat-icon>
<div>
<strong>{{ myCashierName ? myCashierName + ' — already' : 'Already' }} settled for {{ settlementDate }}</strong>
<p>
Cash has already been handed back to the vault for this date. To correct an error, a manager must reverse the
existing settlement first.
</p>
@if (isSupervisor) {
<button type="button" class="pg-btn" style="margin-top: 10px" (click)="pageState = 'no-cashier'">
<mat-icon>arrow_back</mat-icon> Select Different Cashier
</button>
}
</div>
</div>
}
<!-- ── FORM ── -->
@if (pageState === 'form') {
<!-- Cashier identity + teller + date -->
<div class="pg-toolbar pg-toolbar--mat">
<div class="settle-who">
<mat-icon>{{ isSupervisor ? 'manage_accounts' : 'person' }}</mat-icon>
<span class="settle-who__name">{{ myCashierName }}</span>
<span class="settle-who__label">{{ isSupervisor ? 'Cashier (Supervisor)' : 'Cashier (From)' }}</span>
@if (isSupervisor) {
<button
type="button"
class="pg-btn"
style="margin-left: 8px; font-size: 11px; padding: 2px 8px; height: 26px"
(click)="pageState = 'no-cashier'"
>
Change
</button>
}
</div>
@if (tellers.length === 1) {
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>Teller / Vault (To) *</mat-label>
<input matInput type="text" [value]="toParty" readonly />
</mat-form-field>
} @else {
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>Teller / Vault (To) *</mat-label>
<mat-select [value]="selectedTellerId" (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>Settlement Date</mat-label>
<input
matInput
type="date"
[value]="settlementDate"
(change)="settlementDate = $any($event.target).value; loadData()"
/>
</mat-form-field>
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="pg-note-field">
<mat-label>Note (Optional)</mat-label>
<input matInput type="text" [value]="note" (input)="note = $any($event.target).value" />
</mat-form-field>
</div>
<!-- No expected balance -->
@if (rows.length === 0) {
<div class="settle-alert settle-alert--warn">
<mat-icon>warning</mat-icon>
<div>
<strong>No expected balance found</strong>
<p>
No cash has been allocated to your drawer for {{ settlementDate }}. Ensure vault-to-cashier transfer was
done in the morning.
</p>
</div>
</div>
}
<!-- Morning Opening Balance summary panel -->
@if (morningTotal > 0) {
<div class="morning-panel">
<div class="morning-panel__head">
<mat-icon>wb_sunny</mat-icon>
<span>Morning Opening Balance <em>(Cash received from vault today)</em></span>
<strong class="morning-panel__total">₹{{ morningTotal | number: '1.2-2' }}</strong>
</div>
<div class="morning-panel__body">
@if (noteRows.length > 0) {
<div class="morning-panel__section-label">Notes — ₹{{ morningNoteTotal | number: '1.0-0' }}</div>
<div class="morning-panel__pills">
@for (row of noteRows; track row.denominationId) {
@if (row.morningCount > 0) {
<span class="morning-pill"
>₹{{ row.denominationValue }}<span class="morning-pill__x">×{{ row.morningCount }}</span></span
>
}
}
</div>
}
@if (coinRows.length > 0) {
<div class="morning-panel__section-label morning-panel__section-label--coin">
Coins — ₹{{ morningCoinTotal | number: '1.0-0' }}
</div>
<div class="morning-panel__pills">
@for (row of coinRows; track row.denominationId) {
@if (row.morningCount > 0) {
<span class="morning-pill morning-pill--coin"
>₹{{ row.denominationValue }}<span class="morning-pill__x">×{{ row.morningCount }}</span></span
>
}
}
</div>
}
</div>
</div>
}
<!-- Denomination table -->
@if (rows.length > 0) {
<div class="pg-card">
<!-- Notes -->
@if (noteRows.length > 0) {
<div class="pos-section-label">Notes</div>
<table class="pg-table settle-table">
<thead>
<tr>
<th>Denomination</th>
<th class="num settle-col-morning" title="What cashier received from vault this morning">Morning</th>
<th class="num">System Expected</th>
<th style="text-align: center">Physical Count</th>
<th class="num">Variance</th>
</tr>
</thead>
<tbody>
@for (row of noteRows; track row.denominationId) {
<tr [class.row-excess]="varianceFor(row) > 0" [class.row-short]="varianceFor(row) < 0">
<td>
<span class="pg-chip">₹{{ row.denominationValue }}</span>
</td>
<td class="num settle-morning">{{ row.morningCount || '—' }}</td>
<td class="num settle-expected">{{ row.expectedCount }}</td>
<td style="text-align: center">
<div class="pg-step" style="margin: 0 auto">
<button type="button" (click)="bump(row, -1)" [disabled]="row.physicalCount === 0">−</button>
<input
type="number"
min="0"
[value]="row.physicalCount"
(input)="updatePhysical(row, $any($event.target).value)"
/>
<button type="button" (click)="bump(row, 1)">+</button>
</div>
</td>
<td
class="num"
[class.settle-var--pos]="varianceFor(row) > 0"
[class.settle-var--neg]="varianceFor(row) < 0"
[class.settle-var--zero]="varianceFor(row) === 0"
>
{{ varianceFor(row) > 0 ? '+' : '' }}{{ varianceFor(row) }}
@if (varianceFor(row) !== 0) {
<span class="settle-var-amt"
>(₹{{ varianceFor(row) * row.denominationValue | number: '1.0-0' }})</span
>
}
</td>
</tr>
}
</tbody>
</table>
}
<!-- Coins -->
@if (coinRows.length > 0) {
<div class="pos-section-label pos-section-label--coin">Coins</div>
<table class="pg-table settle-table">
<thead>
<tr>
<th>Denomination</th>
<th class="num settle-col-morning" title="What cashier received from vault this morning">Morning</th>
<th class="num">System Expected</th>
<th style="text-align: center">Physical Count</th>
<th class="num">Variance</th>
</tr>
</thead>
<tbody>
@for (row of coinRows; track row.denominationId) {
<tr [class.row-excess]="varianceFor(row) > 0" [class.row-short]="varianceFor(row) < 0">
<td>
<span class="pg-chip pg-chip--coin">₹{{ row.denominationValue }}</span>
</td>
<td class="num settle-morning">{{ row.morningCount || '—' }}</td>
<td class="num settle-expected">{{ row.expectedCount }}</td>
<td style="text-align: center">
<div class="pg-step" style="margin: 0 auto">
<button type="button" (click)="bump(row, -1)" [disabled]="row.physicalCount === 0">−</button>
<input
type="number"
min="0"
[value]="row.physicalCount"
(input)="updatePhysical(row, $any($event.target).value)"
/>
<button type="button" (click)="bump(row, 1)">+</button>
</div>
</td>
<td
class="num"
[class.settle-var--pos]="varianceFor(row) > 0"
[class.settle-var--neg]="varianceFor(row) < 0"
[class.settle-var--zero]="varianceFor(row) === 0"
>
{{ varianceFor(row) > 0 ? '+' : '' }}{{ varianceFor(row) }}
@if (varianceFor(row) !== 0) {
<span class="settle-var-amt"
>(₹{{ varianceFor(row) * row.denominationValue | number: '1.0-0' }})</span
>
}
</td>
</tr>
}
</tbody>
</table>
}
</div>
<!-- Variance reason banner (only when variance exists) -->
@if (hasVariance) {
<div
class="settle-variance-banner"
[class.settle-variance-banner--excess]="totalVariance > 0"
[class.settle-variance-banner--short]="totalVariance < 0"
>
<mat-icon>{{ totalVariance > 0 ? 'add_circle_outline' : 'remove_circle_outline' }}</mat-icon>
<div class="settle-variance-text">
<strong>
{{ totalVariance > 0 ? 'Excess' : 'Shortage' }}: ₹{{
(totalVariance < 0 ? -totalVariance : totalVariance) | number: '1.2-2'
}}
</strong>
<span>Physical count differs from system expected. A reason is required to proceed.</span>
</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="settle-reason-field">
<mat-label>Variance Reason *</mat-label>
<input
matInput
type="text"
[value]="varianceReason"
(input)="varianceReason = $any($event.target).value"
placeholder="e.g. Customer gave torn note, rounding difference"
/>
</mat-form-field>
</div>
}
<!-- Footer totals + submit -->
<div class="pg-total-bar settle-footer">
<div class="settle-totals">
<span class="settle-totals__item">
System Expected: <strong>₹{{ totalExpected | number: '1.2-2' }}</strong>
</span>
<span class="settle-totals__sep">|</span>
<span class="settle-totals__item">
Physical Count: <strong>₹{{ totalPhysical | number: '1.2-2' }}</strong>
</span>
@if (hasVariance) {
<span class="settle-totals__sep">|</span>
<span
class="settle-totals__item"
[class.settle-var--pos]="totalVariance > 0"
[class.settle-var--neg]="totalVariance < 0"
>
Variance:
<strong>{{ totalVariance > 0 ? '+' : '' }}₹{{ totalVariance | number: '1.2-2' }}</strong>
</span>
}
</div>
<div class="settle-footer__actions">
@if (!selectedTellerId) {
<span class="settle-hint">Select a teller / vault to enable submit</span>
}
@if (hasVariance && !varianceReason.trim()) {
<span class="settle-hint settle-hint--warn">Enter variance reason to submit</span>
}
<button type="button" class="pg-btn pg-btn--primary" [disabled]="!valid || saving" (click)="submit()">
<mat-icon>check_circle</mat-icon>
{{ saving ? 'Saving…' : 'Record Settlement' }}
</button>
</div>
</div>
}
}
</div>