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

335 lines
14 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 · RECONCILIATION</div>
<h1 class="pg-title">Cash Reconciliation</h1>
<p class="pg-sub">Compare physical denomination count with system expected balance. Highlights variance.</p>
</div>
</header>
<mat-tab-group animationDuration="150ms">
<!-- ── Entry Tab ── -->
<mat-tab label="New Reconciliation">
<div class="pg-tab-body">
<div class="pg-toolbar pg-toolbar--mat">
@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]="selectedTellerId" (selectionChange)="onTellerSelect($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]="selectedCashierId"
(selectionChange)="onCashierSelect($event.value)"
[disabled]="!selectedTellerId"
>
@for (c of cashiers; track c.id) {
<mat-option [value]="c.id">{{ c.label }}</mat-option>
}
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" subscriptSizing="dynamic">
<mat-label>Date</mat-label>
<input
matInput
type="date"
[value]="reconDate"
(change)="reconDate = $any($event.target).value; fetchExpected()"
/>
</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>
@if (loadingExpected) {
<div class="pg-empty">Loading expected balances…</div>
}
@if (!loadingExpected && rows.length > 0) {
<!-- Morning Opening Balance 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>
}
<div class="pg-card">
@if (noteRows.length > 0) {
<div class="pos-section-label">Notes</div>
<table class="pg-table">
<thead>
<tr>
<th>Denomination</th>
<th class="num recon-col-morning" title="Cash received from vault this morning">Morning</th>
<th style="text-align: center">Expected</th>
<th style="text-align: center">Physical</th>
<th class="num">Variance</th>
</tr>
</thead>
<tbody>
@for (row of noteRows; track row.denominationId) {
<tr [class.row-excess]="row.varianceCount > 0" [class.row-short]="row.varianceCount < 0">
<td>
<span class="pg-chip">₹{{ row.denominationValue }}</span>
</td>
<td class="num recon-morning">{{ row.morningCount || '—' }}</td>
<td style="text-align: center; color: #64748b; font-weight: 600">{{ row.expectedCount }}</td>
<td style="text-align: center">
<div class="pg-step" style="margin: 0 auto">
<button
type="button"
(click)="updatePhysical(row, row.physicalCount - 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)="updatePhysical(row, row.physicalCount + 1)">+</button>
</div>
</td>
<td
class="num"
[style.color]="
row.varianceCount > 0 ? '#16a34a' : row.varianceCount < 0 ? '#dc2626' : '#94a3b8'
"
>
{{ row.varianceCount > 0 ? '+' : '' }}{{ row.varianceCount }} (₹{{
row.varianceCount * row.denominationValue | number: '1.0-0'
}})
</td>
</tr>
}
</tbody>
</table>
}
@if (coinRows.length > 0) {
<div class="pos-section-label pos-section-label--coin">Coins</div>
<table class="pg-table">
<thead>
<tr>
<th>Denomination</th>
<th class="num recon-col-morning" title="Cash received from vault this morning">Morning</th>
<th style="text-align: center">Expected</th>
<th style="text-align: center">Physical</th>
<th class="num">Variance</th>
</tr>
</thead>
<tbody>
@for (row of coinRows; track row.denominationId) {
<tr [class.row-excess]="row.varianceCount > 0" [class.row-short]="row.varianceCount < 0">
<td>
<span class="pg-chip pg-chip--coin">₹{{ row.denominationValue }}</span>
</td>
<td class="num recon-morning">{{ row.morningCount || '—' }}</td>
<td style="text-align: center; color: #64748b; font-weight: 600">{{ row.expectedCount }}</td>
<td style="text-align: center">
<div class="pg-step" style="margin: 0 auto">
<button
type="button"
(click)="updatePhysical(row, row.physicalCount - 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)="updatePhysical(row, row.physicalCount + 1)">+</button>
</div>
</td>
<td
class="num"
[style.color]="
row.varianceCount > 0 ? '#16a34a' : row.varianceCount < 0 ? '#dc2626' : '#94a3b8'
"
>
{{ row.varianceCount > 0 ? '+' : '' }}{{ row.varianceCount }} (₹{{
row.varianceCount * row.denominationValue | number: '1.0-0'
}})
</td>
</tr>
}
</tbody>
</table>
}
</div>
@if (totalVariance !== 0) {
<div
class="settle-variance-banner"
[class.settle-variance-banner--excess]="totalVariance > 0"
[class.settle-variance-banner--short]="totalVariance < 0"
style="margin: 8px 0 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>Variance reason is required to save reconciliation.</span>
</div>
<mat-form-field appearance="outline" subscriptSizing="dynamic" style="flex-shrink: 0; width: 300px">
<mat-label>Variance Reason *</mat-label>
<input
matInput
type="text"
[value]="varianceReason"
(input)="varianceReason = $any($event.target).value"
placeholder="e.g. Rounding, soiled note returned"
/>
</mat-form-field>
</div>
}
<div class="pg-total-bar">
<span>
Expected: <strong>₹{{ totalExpected | number: '1.2-2' }}</strong> &nbsp;|&nbsp; Physical:
<strong>₹{{ totalPhysical | number: '1.2-2' }}</strong> &nbsp;|&nbsp; Variance:
<strong [style.color]="totalVariance === 0 ? '#16a34a' : '#dc2626'">
{{ totalVariance >= 0 ? '+' : '' }}₹{{ totalVariance | number: '1.2-2' }}
</strong>
<span class="badge" [ngClass]="statusClass(statusLabel)">{{ statusLabel }}</span>
</span>
<div style="display: flex; align-items: center; gap: 10px">
@if (totalVariance !== 0 && !varianceReason.trim()) {
<span style="font-size: 12px; color: #d97706; font-weight: 600">Enter variance reason to save</span>
}
<button type="button" class="pg-btn pg-btn--primary" [disabled]="!valid || saving" (click)="submit()">
<mat-icon>save</mat-icon> Save Reconciliation
</button>
</div>
</div>
}
@if (!loadingExpected && rows.length === 0 && selectedCashierId) {
<div class="pg-empty">No balance data found for this cashier.</div>
}
@if (!selectedCashierId) {
<div class="pg-empty">Select a teller and cashier to load expected balances.</div>
}
</div>
</mat-tab>
<!-- ── History Tab ── -->
<mat-tab label="History">
<div class="pg-tab-body">
@if (loadingHistory) {
<div class="pg-empty">Loading…</div>
}
@if (!loadingHistory && history.length === 0) {
<div class="pg-empty">No reconciliation records yet.</div>
}
@if (!loadingHistory && history.length > 0) {
<div class="pg-card">
<table class="pg-table">
<thead>
<tr>
<th>Date</th>
<th>Cashier</th>
<th class="num">Expected</th>
<th class="num">Physical</th>
<th class="num">Variance</th>
<th style="text-align: center">Status</th>
<th>By</th>
</tr>
</thead>
<tbody>
@for (r of history; track r.id) {
<tr>
<td>{{ r.reconDate }}</td>
<td>{{ r.cashierName || r.cashierId }}</td>
<td class="num">₹{{ r.expectedAmount | number: '1.2-2' }}</td>
<td class="num">₹{{ r.physicalAmount | number: '1.2-2' }}</td>
<td class="num" [style.color]="r.variance > 0 ? '#16a34a' : r.variance < 0 ? '#dc2626' : '#94a3b8'">
{{ r.variance >= 0 ? '+' : '' }}₹{{ r.variance | number: '1.2-2' }}
</td>
<td style="text-align: center">
<span class="badge" [ngClass]="statusClass(r.status)">{{ r.status }}</span>
</td>
<td>{{ r.performedByUsername }}</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
</mat-tab>
</mat-tab-group>
</div>