- Transaction Entry's manual GL-entry screen was recording its denomination breakdown against whichever GL account the teller selected, not the vault account whose physical notes actually moved -- resolve and use the Cash Vault GL's own code/name dynamically instead - Closing Cash Report's GL Entries table showed only the one account its denomination row knew about; now shows both the debit and credit account of each voucher (backend now returns debitParty/creditParty), matching what My Vouchers already shows for the same entries Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
489 lines
21 KiB
HTML
489 lines
21 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>
|
||
<h1 class="pg-title">Closing Cash Report</h1>
|
||
<p class="pg-sub">How much cash you started with, what came in and went out, and how much you must hand back.</p>
|
||
</div>
|
||
<div class="pg-toolbar">
|
||
<label class="pg-field">
|
||
<span>Date</span>
|
||
<input type="date" [value]="date" (change)="date = $any($event.target).value" />
|
||
</label>
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="load()" [disabled]="loading">
|
||
<mat-icon>search</mat-icon> Load
|
||
</button>
|
||
<button type="button" class="pg-btn" [class.pg-btn--active]="showGl" (click)="toggleGl()">
|
||
<mat-icon>account_balance</mat-icon> GL View
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- The day's own cash, leg by leg, ending in what should physically be in the drawer.
|
||
A cashier carries no balance: they draw from the vault in the morning and hand back at close,
|
||
so this is the figure a physical count is checked against.
|
||
|
||
This used to sit under a second strip reading Deposits − Withdrawals = Net Counter Movement.
|
||
That strip stated the same two figures again and then a net the cashier has no use for: it
|
||
excludes the vault legs, so on a day the vault gave ₹600 and customer business cancelled out
|
||
it read ₹0 while the drawer held ₹600. Two panels, four repeated figures, and the one that
|
||
came first was the one to ignore. -->
|
||
@if (!showGl && scopedToCashier && hasTodayLegs) {
|
||
<!-- Says what the strip is before the reader meets five figures and a running sum. Somebody
|
||
opening this screen for the first time has no way to know that the last tile is the one
|
||
that matters. -->
|
||
<p class="closing-lede">Your cash for the day, step by step</p>
|
||
<div class="closing-summary closing-summary--balance">
|
||
<div class="closing-summary__item closing-summary__item--in">
|
||
<span class="closing-summary__label">Cash you got this morning</span>
|
||
<span class="closing-summary__value">₹{{ todayVaultIn | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="closing-summary__sep">+</div>
|
||
<div class="closing-summary__item closing-summary__item--in">
|
||
<span class="closing-summary__label">Cash customers gave you</span>
|
||
<span class="closing-summary__value">₹{{ depositTotalWithDd | number: '1.2-2' }}</span>
|
||
@if (ddCashTotal > 0) {
|
||
<!-- Named on the tile as well as in the table: this part of the total carries no
|
||
denomination, so without saying so the figures read as failing to add up. -->
|
||
<span class="closing-summary__note">
|
||
₹{{ ddCashTotal | number: '1.2-2' }} of this was for {{ ddCashCount }} draft{{
|
||
ddCashCount === 1 ? '' : 's'
|
||
}}, notes not listed
|
||
</span>
|
||
}
|
||
</div>
|
||
<div class="closing-summary__sep">−</div>
|
||
<div class="closing-summary__item closing-summary__item--out">
|
||
<span class="closing-summary__label">Cash you gave customers</span>
|
||
<span class="closing-summary__value">₹{{ withdrawalTotal | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="closing-summary__sep">−</div>
|
||
<div class="closing-summary__item closing-summary__item--out">
|
||
<span class="closing-summary__label">Cash already returned</span>
|
||
<span class="closing-summary__value">₹{{ todaySettledOut | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="closing-summary__sep">=</div>
|
||
<!-- The figure the day ends on. Named for what the cashier does with it — hand this back to
|
||
the vault — rather than "closing balance", which reads as something they keep. -->
|
||
<div
|
||
class="closing-summary__item"
|
||
[class.closing-summary__item--net-pos]="expectedInDrawer >= 0"
|
||
[class.closing-summary__item--net-neg]="expectedInDrawer < 0"
|
||
>
|
||
<span class="closing-summary__label">Cash left with you now</span>
|
||
<span class="closing-summary__value">₹{{ expectedInDrawer | number: '1.2-2' }}</span>
|
||
</div>
|
||
</div>
|
||
<p class="closing-note">
|
||
Count your cash now — it should come to ₹{{ expectedInDrawer | number: '1.2-2' }}. Hand that amount back and
|
||
your day is done.
|
||
</p>
|
||
<!-- Cashier-to-cashier hand-overs are left out of the row above to keep it to the four legs a
|
||
teller sees, and stated here when they happened rather than silently folded in. -->
|
||
@if ((todayInterCashierIn ?? 0) > 0 || (todayInterCashierOut ?? 0) > 0) {
|
||
<p class="closing-note">
|
||
This already counts cash swapped with another cashier — ₹{{ todayInterCashierIn | number: '1.2-2' }} taken
|
||
from them, ₹{{ todayInterCashierOut | number: '1.2-2' }} given to them.
|
||
</p>
|
||
}
|
||
}
|
||
|
||
@if (!showGl) {
|
||
<div class="pg-card">
|
||
@if (noteRows.length > 0 || ddCashTotal > 0) {
|
||
<div class="pos-section-label">Notes</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Note</th>
|
||
<!-- Counted in pieces, because that is what a teller does with notes. Nine columns of
|
||
pieces-and-amount for every leg was a reconciliation nobody could read at a
|
||
counter; these five are the sentence the day actually makes: this many came in
|
||
from the vault, this many over the counter, this many went out, this many are
|
||
left, and that is worth this much. -->
|
||
<th class="num col-in">Came in</th>
|
||
<th class="num col-out">Went out</th>
|
||
<th class="num col-net">Left with you</th>
|
||
<th class="num col-net">Worth</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of noteRows; track r.denominationId) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip">₹{{ r.denominationValue }}</span>
|
||
</td>
|
||
<!-- Vault and counter added together: to a teller both are notes arriving, and
|
||
splitting them made the reader do the sum. A dash rather than a zero where
|
||
nothing happened, so the eye passes over it. -->
|
||
<td class="num col-in">{{ r.vaultInCount + r.depositCount || '—' }}</td>
|
||
<td class="num col-out">{{ r.withdrawalCount + r.settledOutCount || '—' }}</td>
|
||
<td
|
||
class="num col-net"
|
||
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
|
||
>
|
||
{{ r.netCount }}
|
||
</td>
|
||
<td
|
||
class="num col-net"
|
||
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
|
||
>
|
||
{{ r.netCount >= 0 ? '' : '−' }}₹{{
|
||
(r.netAmount < 0 ? -r.netAmount : r.netAmount) | number: '1.2-2'
|
||
}}
|
||
</td>
|
||
</tr>
|
||
}
|
||
|
||
<!-- Cash taken for a Demand Draft, on the deposit side where it belongs. It has no
|
||
denomination to sit against -- the notes were never itemised -- so the piece
|
||
counts show a dash rather than a zero, which would read as "no notes". -->
|
||
@if (ddCashTotal > 0) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip">Notes not listed</span>
|
||
<span class="closing-row__hint"> {{ ddCashCount }} cash DD{{ ddCashCount === 1 ? '' : 's' }} </span>
|
||
</td>
|
||
<td class="num col-in">—</td>
|
||
<td class="num col-out">—</td>
|
||
<td class="num col-net">—</td>
|
||
<td class="num col-net" style="color: #16a34a">₹{{ ddCashTotal | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<!-- Totals, so the table reconciles against the summary above rather than having to be
|
||
added up by hand — and so the deposit column visibly accounts for the DD row. -->
|
||
<tfoot>
|
||
<tr>
|
||
<td>Total</td>
|
||
<td class="num col-in">{{ noteVaultInCount + noteDepositCount || '—' }}</td>
|
||
<td class="num col-out">{{ noteWithdrawalCount + noteSettledOutCount || '—' }}</td>
|
||
<td class="num col-net">{{ noteNetCount }}</td>
|
||
<td class="num col-net">
|
||
{{ noteNetAmount >= 0 ? '' : '−' }}₹{{
|
||
(noteNetAmount < 0 ? -noteNetAmount : noteNetAmount) | number: '1.2-2'
|
||
}}
|
||
</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
}
|
||
|
||
@if (coinRows.length > 0) {
|
||
<div class="pos-section-label pos-section-label--coin">Coins</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Coin</th>
|
||
<th class="num col-in">Came in</th>
|
||
<th class="num col-out">Went out</th>
|
||
<th class="num col-net">Left with you</th>
|
||
<th class="num col-net">Worth</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of coinRows; track r.denominationId) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip pg-chip--coin">₹{{ r.denominationValue }}</span>
|
||
</td>
|
||
<!-- Vault and counter added together: to a teller both are notes arriving, and
|
||
splitting them made the reader do the sum. A dash rather than a zero where
|
||
nothing happened, so the eye passes over it. -->
|
||
<td class="num col-in">{{ r.vaultInCount + r.depositCount || '—' }}</td>
|
||
<td class="num col-out">{{ r.withdrawalCount + r.settledOutCount || '—' }}</td>
|
||
<td
|
||
class="num col-net"
|
||
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
|
||
>
|
||
{{ r.netCount }}
|
||
</td>
|
||
<td
|
||
class="num col-net"
|
||
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
|
||
>
|
||
{{ r.netCount >= 0 ? '' : '−' }}₹{{
|
||
(r.netAmount < 0 ? -r.netAmount : r.netAmount) | number: '1.2-2'
|
||
}}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
}
|
||
|
||
<!-- Not shown when a Demand Draft row is on the table: there is something to read then, and
|
||
an "empty" notice under it contradicts it. -->
|
||
@if (loaded && rows.length === 0 && exchangeRows.length === 0 && ddCashTotal === 0) {
|
||
<!-- A day with no counter work is not an empty report: the cashier is still holding
|
||
whatever they held yesterday, and that balance is shown in the strip above. Saying
|
||
only "no data" here read as though the drawer were empty. -->
|
||
<div class="pg-empty">
|
||
@if (scopedToCashier && hasTodayLegs && expectedInDrawer !== 0) {
|
||
No cash crossed the counter on this date. The drawer should hold ₹{{ expectedInDrawer | number: '1.2-2' }}
|
||
from the day's vault and settlement legs.
|
||
} @else {
|
||
No cash denomination data for this date.
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
<!-- end !showGl -->
|
||
|
||
<!-- GL View Panel -->
|
||
@if (showGl) {
|
||
<div class="gl-card">
|
||
<!-- Card Header -->
|
||
<div class="gl-card__header">
|
||
<div class="gl-card__header-left">
|
||
<div class="gl-card__icon">
|
||
<mat-icon>account_balance</mat-icon>
|
||
</div>
|
||
<div>
|
||
<div class="gl-card__title">GL Entries</div>
|
||
<div class="gl-card__sub">Cashier transaction ledger for {{ date }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="gl-card__totals">
|
||
<div class="gl-card__total gl-card__total--dr">
|
||
<span class="gl-card__total-label">Total DR</span>
|
||
<span class="gl-card__total-value">₹{{ glTotalDr | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="gl-card__total-sep">|</div>
|
||
<div class="gl-card__total gl-card__total--cr">
|
||
<span class="gl-card__total-label">Total CR</span>
|
||
<span class="gl-card__total-value">₹{{ glTotalCr | number: '1.2-2' }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filters Bar -->
|
||
<div class="gl-card__filters">
|
||
<label class="pg-field">
|
||
<span>Teller Head</span>
|
||
<select class="pg-select" [(ngModel)]="selectedTellerId" (change)="onTellerChange()">
|
||
<option [ngValue]="null">All Tellers</option>
|
||
@for (t of tellers; track t.id) {
|
||
<option [ngValue]="t.id">{{ t.name }}</option>
|
||
}
|
||
</select>
|
||
</label>
|
||
@if (selectedTellerId) {
|
||
<label class="pg-field">
|
||
<span>Cashier</span>
|
||
<select class="pg-select" [(ngModel)]="selectedCashierId" (change)="onCashierChange()">
|
||
<option [ngValue]="null">All Cashiers</option>
|
||
@for (c of cashiers; track c.id) {
|
||
<option [ngValue]="c.id">{{ c.staffName }}</option>
|
||
}
|
||
</select>
|
||
</label>
|
||
}
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="loadGl()" [disabled]="glLoading">
|
||
<mat-icon>refresh</mat-icon> Refresh
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Table -->
|
||
@if (glLoading) {
|
||
<div class="pg-empty">Loading...</div>
|
||
} @else if (glRows.length === 0 && glLoaded) {
|
||
<div class="pg-empty">No entries found for this date and filter.</div>
|
||
} @else if (glRows.length > 0) {
|
||
<table class="pg-table gl-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="gl-th--voucher">Voucher No</th>
|
||
<th class="gl-th--date">Date</th>
|
||
<th>Debit</th>
|
||
<th>Credit</th>
|
||
<th class="gl-th--pt">Payment Type</th>
|
||
<th class="gl-th--teller">Teller</th>
|
||
<th class="num gl-th--dr">DR (₹)</th>
|
||
<th class="num gl-th--cr">CR (₹)</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of glRows; track $index) {
|
||
<tr [class.gl-row--dr]="r.dr > 0" [class.gl-row--cr]="r.cr > 0">
|
||
<td class="gl-voucher">
|
||
@if (r.txnSource === 'GL') {
|
||
<span class="gl-badge">GL</span>
|
||
}
|
||
{{ r.voucherNo }}
|
||
</td>
|
||
<td class="gl-date">
|
||
<div>{{ r.txnDate }}</div>
|
||
@if (r.txnTime) {
|
||
<div class="gl-time">{{ r.txnTime }}</div>
|
||
}
|
||
</td>
|
||
<!-- Both legs of the voucher, the same pair My Vouchers shows — not just the one
|
||
side this row's own denomination breakdown happens to know about. Falls back
|
||
to that one side (acNo/acName) when the voucher predates m_voucher_line. -->
|
||
<td class="gl-acname">
|
||
<div>{{ r.debitParty || r.acName }}</div>
|
||
@if (r.debitAccount || r.acNo) {
|
||
<div class="gl-acno">{{ r.debitAccount || r.acNo }}</div>
|
||
}
|
||
</td>
|
||
<td class="gl-acname">
|
||
@if (r.creditParty) {
|
||
<div>{{ r.creditParty }}</div>
|
||
@if (r.creditAccount) {
|
||
<div class="gl-acno">{{ r.creditAccount }}</div>
|
||
}
|
||
} @else {
|
||
<span class="gl-pt-none">—</span>
|
||
}
|
||
</td>
|
||
<td class="gl-pt">
|
||
@if (r.paymentType) {
|
||
<span class="gl-pt-badge">{{ r.paymentType }}</span>
|
||
} @else {
|
||
<span class="gl-pt-none">—</span>
|
||
}
|
||
</td>
|
||
<td class="gl-teller">{{ r.tellerName || '—' }}</td>
|
||
<td class="num gl-amt--dr">
|
||
@if (r.dr > 0) {
|
||
₹{{ r.dr | number: '1.2-2' }}
|
||
}
|
||
</td>
|
||
<td class="num gl-amt--cr">
|
||
@if (r.cr > 0) {
|
||
₹{{ r.cr | number: '1.2-2' }}
|
||
}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="gl-foot">
|
||
<td colspan="6" class="gl-foot__label">Total (All Pages)</td>
|
||
<td class="num gl-amt--dr gl-foot__val">₹{{ glTotalDr | number: '1.2-2' }}</td>
|
||
<td class="num gl-amt--cr gl-foot__val">₹{{ glTotalCr | number: '1.2-2' }}</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
<mat-paginator
|
||
[length]="glTotal"
|
||
[pageSize]="glPageSize"
|
||
[pageSizeOptions]="glPageSizeOptions"
|
||
[pageIndex]="glOffset / glPageSize"
|
||
showFirstLastButtons
|
||
(page)="onGlPageChange($event)"
|
||
></mat-paginator>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@if (!showGl && exchangeRows.length > 0) {
|
||
<div class="pg-card">
|
||
<div class="closing-exchange-head">
|
||
<mat-icon>swap_horiz</mat-icon>
|
||
<div>
|
||
<div class="closing-exchange-title">Cash Exchanges</div>
|
||
<div class="closing-exchange-sub">Denomination swaps recorded on this date (net cash impact: ₹0.00)</div>
|
||
</div>
|
||
</div>
|
||
|
||
@if (exchangeNoteRows.length > 0) {
|
||
<div class="pos-section-label">Notes</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</th>
|
||
<th class="num col-in">Received Pcs</th>
|
||
<th class="num col-in">Received Amt</th>
|
||
<th class="num col-out">Given Pcs</th>
|
||
<th class="num col-out">Given Amt</th>
|
||
<th class="num col-net">Net Pcs</th>
|
||
<th class="num col-net">Net Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of exchangeNoteRows; track r.denominationId) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip">₹{{ r.denominationValue }}</span>
|
||
</td>
|
||
<td class="num col-in">{{ r.receivedCount }}</td>
|
||
<td class="num col-in">₹{{ r.receivedAmount | number: '1.2-2' }}</td>
|
||
<td class="num col-out">{{ r.givenCount }}</td>
|
||
<td class="num col-out">₹{{ r.givenAmount | number: '1.2-2' }}</td>
|
||
<td
|
||
class="num col-net"
|
||
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
|
||
>
|
||
{{ r.netCount > 0 ? '+' : '' }}{{ r.netCount }}
|
||
</td>
|
||
<td
|
||
class="num col-net"
|
||
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
|
||
>
|
||
{{ r.netCount >= 0 ? '' : '−' }}₹{{
|
||
(r.netAmount < 0 ? -r.netAmount : r.netAmount) | number: '1.2-2'
|
||
}}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
}
|
||
|
||
@if (exchangeCoinRows.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 col-in">Received Pcs</th>
|
||
<th class="num col-in">Received Amt</th>
|
||
<th class="num col-out">Given Pcs</th>
|
||
<th class="num col-out">Given Amt</th>
|
||
<th class="num col-net">Net Pcs</th>
|
||
<th class="num col-net">Net Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of exchangeCoinRows; track r.denominationId) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip pg-chip--coin">₹{{ r.denominationValue }}</span>
|
||
</td>
|
||
<td class="num col-in">{{ r.receivedCount }}</td>
|
||
<td class="num col-in">₹{{ r.receivedAmount | number: '1.2-2' }}</td>
|
||
<td class="num col-out">{{ r.givenCount }}</td>
|
||
<td class="num col-out">₹{{ r.givenAmount | number: '1.2-2' }}</td>
|
||
<td
|
||
class="num col-net"
|
||
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
|
||
>
|
||
{{ r.netCount > 0 ? '+' : '' }}{{ r.netCount }}
|
||
</td>
|
||
<td
|
||
class="num col-net"
|
||
[style.color]="r.netCount > 0 ? '#16a34a' : r.netCount < 0 ? '#dc2626' : '#94a3b8'"
|
||
>
|
||
{{ r.netCount >= 0 ? '' : '−' }}₹{{
|
||
(r.netAmount < 0 ? -r.netAmount : r.netAmount) | number: '1.2-2'
|
||
}}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|