my-work.component (My Vouchers): - The five summary tiles (Received/Payments/Journal/Entered/Approved) were bound directly to the server's day-total fields, so they stayed fixed at the combined "entered + approved" figure no matter what the Side/My part dropdowns were set to. They're now pinned to this user's own entered rows only (enteredRows), independent of both filters, so "how much did I personally put through today" reads correctly regardless of what the table below happens to be filtered to. The table and its dropdowns are unchanged and still default to showing everything. transaction-entry.component (Fund Transfer): - onSave()'s plain bank-transfer branch (SD/CA/CB only) was catching every 'Fund Transfer' row before the GL-aware Disbursement/Collection branch below it could see them, so a Fund Transfer against a GL account was always rejected with "Invalid Account Type" even though that branch already supports it under the same label. Fund Transfer rows that include a GL leg now skip the plain-transfer branch and fall through correctly. my-disbursement-requests.component (Disbursements & Transfer list): - getCreditEntries() only read the stored credit_entries JSON, which holds the full picture for Collection (its GL leg is one of the entries) but not for Disbursement/Fund Transfer, whose GL is the request's own top-level source (gl_account_no/dr_amount) and was never in that array. The DR/CR breakdown for those two types showed only the credit side, looking like half the entry was missing. The GL leg is now synthesised back in as the first row for those two types, matching what actually posted. - Renamed the clerk's "My Disbursements" / "My Disbursement Requests" screen to "Disbursements & Transfer" throughout (nav tab, dropdown menu, route title/breadcrumb, cashier counter link, page header) — the register covers Disbursement, Collection, Fund Transfer and Bank Transfer requests, and the old name read as disbursement-only. "My Requests" (Withdrawal Slip requests, a separate register) is renamed to "Withdrawal Request" for the same reason: neither name said what was actually inside.
710 lines
28 KiB
HTML
710 lines
28 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">BRANCH OPERATIONS · {{ missedOnly ? 'MISSED CLOSE' : 'DAY REGISTER' }}</div>
|
||
<h1 class="pg-title">{{ missedOnly ? 'Missed Close Register' : 'Day Open / Day Close' }}</h1>
|
||
<p class="pg-sub">
|
||
@if (missedOnly) {
|
||
Business days that were opened and never closed. While a day stays open its closing balance never carries
|
||
forward, a back-dated entry can still be posted into it, and any cash difference on that date goes unnoticed.
|
||
} @else {
|
||
The branch's business day, opened each morning and closed each evening. A day left open never carries its
|
||
closing balance forward, and any cash difference on that date goes unnoticed — so the ones still hanging are
|
||
listed first.
|
||
}
|
||
</p>
|
||
</div>
|
||
<div class="pg-toolbar">
|
||
@if (canOpenToday && !missedOnly) {
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="startOpen()">
|
||
<mat-icon>lock_open</mat-icon> Open Day
|
||
</button>
|
||
}
|
||
<button type="button" class="pg-btn" (click)="load()" [disabled]="loading">
|
||
<mat-icon>refresh</mat-icon> Reload
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
@if (errorMsg) {
|
||
<div class="pg-alert pg-alert--error">{{ errorMsg }}</div>
|
||
}
|
||
@if (successMsg) {
|
||
<div class="bd-success">{{ successMsg }}</div>
|
||
}
|
||
|
||
@if (!canOperate && loaded) {
|
||
<div class="ve-note">
|
||
<mat-icon>info</mat-icon>
|
||
You can see the register but not open or close a day — a Clerk, Head Cashier or Bank Manager does that, since a
|
||
cashier works inside the day and cannot declare it closed.
|
||
</div>
|
||
}
|
||
|
||
<!-- Missed closes first: these are the ones that need action. -->
|
||
@if (missed.length > 0) {
|
||
<div class="db-exceptions">
|
||
<div class="db-exceptions__head">
|
||
<mat-icon>warning</mat-icon>
|
||
<strong>{{ missed.length }} business day{{ missed.length === 1 ? '' : 's' }} never closed</strong>
|
||
</div>
|
||
@for (m of missed; track m.id) {
|
||
<div class="bd-missed">
|
||
<span class="pg-chip">{{ m.businessDate }}</span>
|
||
<strong class="bd-missed__age"> open {{ m.daysOpen }} day{{ m.daysOpen === 1 ? '' : 's' }} </strong>
|
||
<span class="db-muted">
|
||
opened {{ timeOnly(m.openedAt) }} by {{ m.openedByName }} · opening ₹{{ m.openingCash | number: '1.2-2' }}
|
||
</span>
|
||
@if (canOperate) {
|
||
<button type="button" class="pg-btn pg-btn--warn bd-missed__act" (click)="startClose(m)">
|
||
Close this day
|
||
</button>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- On the missed-close screen, nothing to report is itself the answer — and has to be stated,
|
||
or the page looks like it failed to load. -->
|
||
@if (missedOnly && loaded && missed.length === 0) {
|
||
<div class="pg-empty">
|
||
<mat-icon>check_circle</mat-icon>
|
||
<p>No missed closes. Every business day opened for this branch has been closed.</p>
|
||
</div>
|
||
}
|
||
|
||
<!-- Current day -->
|
||
@if (!missedOnly && openDay) {
|
||
<div class="bd-current">
|
||
<div class="bd-current__main">
|
||
<span class="db-badge db-badge--pending">DAY OPEN</span>
|
||
<strong class="bd-current__date">{{ openDay.businessDate }}</strong>
|
||
<span class="db-muted">
|
||
opened {{ timeOnly(openDay.openedAt) }} by {{ openDay.openedByName }} · opening cash ₹{{
|
||
openDay.openingCash | number: '1.2-2'
|
||
}}
|
||
</span>
|
||
</div>
|
||
@if (canOperate) {
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="startClose(openDay)">
|
||
<mat-icon>lock</mat-icon> Close Day
|
||
</button>
|
||
}
|
||
</div>
|
||
} @else if (loaded && canOperate) {
|
||
<div class="bd-current bd-current--shut">
|
||
<div class="bd-current__main">
|
||
<span class="db-badge">NO DAY OPEN</span>
|
||
<span class="db-muted">Open the business day before any cash transaction is accepted.</span>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- Open-day form -->
|
||
@if (showOpenForm) {
|
||
<div class="db-panel bd-panel">
|
||
<div class="db-panel__head">
|
||
<strong>Open business day</strong>
|
||
<button type="button" class="db-icon-btn" (click)="cancelOpen()" title="Close">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
</div>
|
||
<div class="db-panel__body">
|
||
<label class="pg-field">
|
||
<span>Business date</span>
|
||
<input type="date" [(ngModel)]="openDate" />
|
||
</label>
|
||
<label class="pg-field">
|
||
<span>Opening cash (leave blank to carry forward)</span>
|
||
<input
|
||
type="number"
|
||
step="0.01"
|
||
[(ngModel)]="openingCash"
|
||
[disabled]="openDenomTotal > 0"
|
||
placeholder="Carried from last close"
|
||
/>
|
||
</label>
|
||
</div>
|
||
<p class="bd-hint">
|
||
@if (hasAutoFilledOpenCounts) {
|
||
Auto-filled below from yesterday's own closing count (₹{{ carriedForwardTotal | number: '1.2-2' }}) — correct
|
||
only the rows where today's physical count differs before opening.
|
||
} @else {
|
||
Left blank with no notes/coins counted below, yesterday's closing figure carries forward automatically — which
|
||
is what keeps the opening balance from drifting. Counting the drawer below is what actually records the
|
||
note/coin stock the branch can check a vault handout against later.
|
||
}
|
||
</p>
|
||
|
||
@if (hasAutoFilledOpenCounts && openLiveDifference !== null) {
|
||
@if (openLiveDifference === 0) {
|
||
<div class="db-cmp db-cmp--good">
|
||
<span class="db-cmp__label">vs. yesterday's close</span>
|
||
<span class="db-cmp__value">Tallied</span>
|
||
</div>
|
||
} @else {
|
||
<div class="db-cmp db-cmp--bad">
|
||
<span class="db-cmp__label">vs. yesterday's close</span>
|
||
<span class="db-cmp__value">
|
||
{{ openLiveDifference > 0 ? 'Excess' : 'Short' }} ₹{{ absValue(openLiveDifference) | number: '1.2-2' }}
|
||
</span>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
<div class="pg-card bd-denom">
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</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="3">Bank Notes</td>
|
||
</tr>
|
||
}
|
||
@for (d of notes; track d.id) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip">{{ d.label }}</span>
|
||
</td>
|
||
<td style="display: flex; flex-direction: column; align-items: center">
|
||
<div class="pg-step">
|
||
<button
|
||
type="button"
|
||
(click)="bumpCount(openCounts, d, -1)"
|
||
[disabled]="countFor(openCounts, d) === 0"
|
||
>
|
||
−
|
||
</button>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
[value]="countFor(openCounts, d)"
|
||
(input)="onCountChange(openCounts, d, $any($event.target).value)"
|
||
/>
|
||
<button type="button" (click)="bumpCount(openCounts, d, 1)">+</button>
|
||
</div>
|
||
@if (hasAutoFilledOpenCounts && countFor(openCounts, d) !== autoOpenCountFor(d)) {
|
||
<span class="db-muted bd-denom__auto">yesterday closed {{ autoOpenCountFor(d) }}</span>
|
||
}
|
||
</td>
|
||
<td class="num">₹{{ subtotalFor(openCounts, d) | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
@if (coins.length) {
|
||
<tr class="pg-section">
|
||
<td colspan="3">Coins</td>
|
||
</tr>
|
||
}
|
||
@for (d of coins; track d.id) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip pg-chip--coin">{{ d.label }}</span>
|
||
</td>
|
||
<td style="display: flex; flex-direction: column; align-items: center">
|
||
<div class="pg-step">
|
||
<button
|
||
type="button"
|
||
(click)="bumpCount(openCounts, d, -1)"
|
||
[disabled]="countFor(openCounts, d) === 0"
|
||
>
|
||
−
|
||
</button>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
[value]="countFor(openCounts, d)"
|
||
(input)="onCountChange(openCounts, d, $any($event.target).value)"
|
||
/>
|
||
<button type="button" (click)="bumpCount(openCounts, d, 1)">+</button>
|
||
</div>
|
||
@if (hasAutoFilledOpenCounts && countFor(openCounts, d) !== autoOpenCountFor(d)) {
|
||
<span class="db-muted bd-denom__auto">yesterday closed {{ autoOpenCountFor(d) }}</span>
|
||
}
|
||
</td>
|
||
<td class="num">₹{{ subtotalFor(openCounts, d) | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (openDenomTotal > 0) {
|
||
<p class="bd-hint">
|
||
Denomination total: <strong>₹{{ openDenomTotal | number: '1.2-2' }}</strong>
|
||
@if (hasAutoFilledOpenCounts) {
|
||
<button type="button" class="pg-btn bd-denom__reset" (click)="resetOpenToAutoFill()">
|
||
Reset to yesterday's count
|
||
</button>
|
||
}
|
||
</p>
|
||
}
|
||
|
||
@if (openLiveDifference !== null && openLiveDifference !== 0) {
|
||
<label class="pg-field bd-panel__wide">
|
||
<span>Why does this differ from yesterday's closing count? (required)</span>
|
||
<input
|
||
type="text"
|
||
[(ngModel)]="openDifferenceReason"
|
||
placeholder="e.g. notes exchanged for smaller denominations, extra cash added from head office"
|
||
/>
|
||
</label>
|
||
}
|
||
|
||
<div class="db-panel__actions">
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="submitOpen()" [disabled]="!canSubmitOpen">
|
||
Open Day
|
||
</button>
|
||
<button type="button" class="pg-btn" (click)="cancelOpen()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- Close-day form -->
|
||
@if (closeTarget) {
|
||
<div class="db-panel bd-panel" [class.bd-panel--late]="isLateClose">
|
||
<div class="db-panel__head">
|
||
<strong>Close business day — {{ closeTarget.businessDate }}</strong>
|
||
<button type="button" class="db-icon-btn" (click)="cancelClose()" title="Close">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
</div>
|
||
|
||
@if (isLateClose) {
|
||
<p class="db-panel__warn">
|
||
This day is being closed after its own date. It will be recorded as a
|
||
<strong>force close</strong>, with the reason below kept permanently against it.
|
||
</p>
|
||
}
|
||
|
||
@if (previewLoading) {
|
||
<p class="db-muted">Working out the day's figure…</p>
|
||
}
|
||
|
||
<!-- The arithmetic, spelled out. Whoever closes the day can see where the expected figure
|
||
comes from instead of being asked to agree with a number they cannot check. -->
|
||
@if (preview) {
|
||
<div class="bd-calc">
|
||
<div class="bd-calc__row">
|
||
<span>Opening cash (carried forward)</span>
|
||
<span class="bd-calc__amt">₹{{ preview.openingCash | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="bd-calc__row bd-calc__row--in">
|
||
<span>+ Cash deposits taken (approved)</span>
|
||
<span class="bd-calc__amt">₹{{ preview.deposits | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="bd-calc__row bd-calc__row--out">
|
||
<span>− Cash paid out (approved withdrawals)</span>
|
||
<span class="bd-calc__amt">₹{{ preview.withdrawals | number: '1.2-2' }}</span>
|
||
</div>
|
||
<div class="bd-calc__row bd-calc__row--total">
|
||
<span>Cash the branch should be holding</span>
|
||
<span class="bd-calc__amt">₹{{ preview.expectedCash | number: '1.2-2' }}</span>
|
||
</div>
|
||
@if (preview.vaultIn > 0 || preview.settledOut > 0) {
|
||
<div class="bd-calc__memo">
|
||
Vault activity today — moves cash within the branch, not counted in the total above: ₹{{
|
||
preview.vaultIn | number: '1.2-2'
|
||
}}
|
||
handed to a cashier, ₹{{ preview.settledOut | number: '1.2-2' }} settled back to the vault.
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- What every cash point under this branch is actually holding right now, fetched
|
||
automatically — so closing the day doesn't mean separately checking Vault Position and
|
||
every cashier's own Cashier Position screen by hand. -->
|
||
@if (eodLoading) {
|
||
<p class="db-muted">Fetching vault and cashier positions…</p>
|
||
}
|
||
|
||
<!-- The question that actually matters at close: does the WHOLE branch tally, not just the
|
||
vault. Only shown once both the book preview and every teller/cashier position have
|
||
loaded, since it adds them together. -->
|
||
@if (branchTotalSummary; as bt) {
|
||
<div class="bd-eod bd-eod--total">
|
||
<div class="bd-eod__head">
|
||
<mat-icon>account_balance_wallet</mat-icon>
|
||
<strong>Branch Total (Vault + All Cashiers)</strong>
|
||
</div>
|
||
<div class="bd-eod__totals">
|
||
<span>Vault ₹{{ bt.vaultTotal | number: '1.2-2' }}</span>
|
||
<span>+ Cashiers ₹{{ bt.cashierTotal | number: '1.2-2' }}</span>
|
||
<span class="bd-eod__totals-sum">= ₹{{ bt.grandTotal | number: '1.2-2' }}</span>
|
||
<span class="db-muted">vs. book ₹{{ bt.expected | number: '1.2-2' }}</span>
|
||
</div>
|
||
@if (bt.difference === 0) {
|
||
<span class="db-badge db-badge--ok">Branch Tallied</span>
|
||
} @else {
|
||
<span class="db-badge db-badge--rejected">
|
||
{{ bt.difference > 0 ? 'Excess' : 'Short' }} ₹{{ absValue(bt.difference) | number: '1.2-2' }}
|
||
</span>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@for (t of eodTellers; track t.tellerId) {
|
||
<div class="bd-eod">
|
||
<div class="bd-eod__head">
|
||
<mat-icon>account_balance</mat-icon>
|
||
<strong>{{ t.tellerName }}</strong>
|
||
<span class="db-muted">
|
||
vault holds
|
||
{{ t.vault?.hasBreakdown ? '₹' + (t.vault.totalAmount | number: '1.2-2') : 'no breakdown recorded' }}
|
||
</span>
|
||
</div>
|
||
|
||
@if (t.vault?.hasBreakdown && t.vault.denominations.length > 0) {
|
||
<div class="bd-eod__denoms">
|
||
@for (d of t.vault.denominations; track d.denominationId) {
|
||
<span class="bd-eod__chip">₹{{ d.denominationValue }} × {{ d.count }}</span>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
@if (t.cashiers.length === 0) {
|
||
<p class="db-muted bd-eod__empty">No cashiers allocated under this vault.</p>
|
||
}
|
||
@for (c of t.cashiers; track c.cashierId) {
|
||
<div class="bd-eod__cashier">
|
||
<div class="bd-eod__cashier-head">
|
||
<span>{{ c.cashierName }}</span>
|
||
<span class="db-muted">
|
||
morning ₹{{ c.morningTotal | number: '1.2-2' }} → current ₹{{ c.currentTotal | number: '1.2-2' }} ·
|
||
{{ c.txnCount ?? '—' }} txn{{ c.txnCount === 1 ? '' : 's' }} today
|
||
</span>
|
||
</div>
|
||
<div class="bd-eod__status">
|
||
@if (c.reconciled === true) {
|
||
<span class="db-badge db-badge--ok">Reconciled</span>
|
||
} @else if (c.reconciled === false) {
|
||
<span class="db-badge db-badge--pending">Not Reconciled</span>
|
||
}
|
||
@if (c.alreadySettled === true) {
|
||
<span class="db-badge db-badge--ok">Settled to Vault</span>
|
||
} @else if (c.alreadySettled === false) {
|
||
<span class="db-badge">Not Settled</span>
|
||
}
|
||
</div>
|
||
@if (c.denominations.length > 0) {
|
||
<div class="bd-eod__denoms">
|
||
@for (d of c.denominations; track d.denominationId) {
|
||
<span
|
||
class="bd-eod__chip"
|
||
[class.bd-eod__chip--up]="d.delta > 0"
|
||
[class.bd-eod__chip--down]="d.delta < 0"
|
||
>
|
||
₹{{ d.denominationValue }}: {{ d.morningCount }} → {{ d.count }}
|
||
@if (d.delta !== 0) {
|
||
({{ d.delta > 0 ? '+' : '' }}{{ d.delta }})
|
||
}
|
||
</span>
|
||
}
|
||
</div>
|
||
} @else {
|
||
<span class="db-muted bd-eod__empty">No balance.</span>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<div class="db-panel__body">
|
||
<label class="pg-field">
|
||
<span>Physical cash counted (or count denominations below)</span>
|
||
<input
|
||
type="number"
|
||
step="0.01"
|
||
[(ngModel)]="closingCash"
|
||
[disabled]="closeDenomTotal > 0"
|
||
placeholder="0.00"
|
||
/>
|
||
</label>
|
||
@if (preview && liveDifference !== null) {
|
||
@if (liveDifference === 0) {
|
||
<div class="db-cmp db-cmp--good">
|
||
<span class="db-cmp__label">Difference</span>
|
||
<span class="db-cmp__value">Tallied</span>
|
||
</div>
|
||
} @else {
|
||
<div class="db-cmp db-cmp--bad">
|
||
<span class="db-cmp__label">Difference</span>
|
||
<span class="db-cmp__value">
|
||
{{ liveDifference > 0 ? 'Excess' : 'Short' }} ₹{{
|
||
liveDifference < 0 ? -liveDifference : (liveDifference | number: '1.2-2')
|
||
}}
|
||
</span>
|
||
</div>
|
||
}
|
||
}
|
||
@if (preview && liveDifference !== 0) {
|
||
<button type="button" class="pg-btn" (click)="useExpected()">Use expected figure</button>
|
||
}
|
||
</div>
|
||
|
||
<p class="bd-hint">
|
||
@if (hasAutoFilledCounts) {
|
||
Auto-filled below from the vault and every cashier's position shown above — correct only the rows where the
|
||
physical count differs, note by note.
|
||
} @else {
|
||
Pre-filled with what the books expect — change it only if the drawer counted something different, and that
|
||
difference is then recorded against the day. Counting the denominations below overrides the typed figure with
|
||
what was actually counted, note by note.
|
||
}
|
||
</p>
|
||
|
||
<div class="pg-card bd-denom">
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Denomination</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="3">Bank Notes</td>
|
||
</tr>
|
||
}
|
||
@for (d of notes; track d.id) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip">{{ d.label }}</span>
|
||
</td>
|
||
<td style="display: flex; flex-direction: column; align-items: center">
|
||
<div class="pg-step">
|
||
<button
|
||
type="button"
|
||
(click)="bumpCount(closeCounts, d, -1)"
|
||
[disabled]="countFor(closeCounts, d) === 0"
|
||
>
|
||
−
|
||
</button>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
[value]="countFor(closeCounts, d)"
|
||
(input)="onCountChange(closeCounts, d, $any($event.target).value)"
|
||
/>
|
||
<button type="button" (click)="bumpCount(closeCounts, d, 1)">+</button>
|
||
</div>
|
||
@if (hasAutoFilledCounts && countFor(closeCounts, d) !== autoCountFor(d)) {
|
||
<span class="db-muted bd-denom__auto">system counted {{ autoCountFor(d) }}</span>
|
||
}
|
||
</td>
|
||
<td class="num">₹{{ subtotalFor(closeCounts, d) | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
@if (coins.length) {
|
||
<tr class="pg-section">
|
||
<td colspan="3">Coins</td>
|
||
</tr>
|
||
}
|
||
@for (d of coins; track d.id) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip pg-chip--coin">{{ d.label }}</span>
|
||
</td>
|
||
<td style="display: flex; flex-direction: column; align-items: center">
|
||
<div class="pg-step">
|
||
<button
|
||
type="button"
|
||
(click)="bumpCount(closeCounts, d, -1)"
|
||
[disabled]="countFor(closeCounts, d) === 0"
|
||
>
|
||
−
|
||
</button>
|
||
<input
|
||
type="number"
|
||
min="0"
|
||
[value]="countFor(closeCounts, d)"
|
||
(input)="onCountChange(closeCounts, d, $any($event.target).value)"
|
||
/>
|
||
<button type="button" (click)="bumpCount(closeCounts, d, 1)">+</button>
|
||
</div>
|
||
@if (hasAutoFilledCounts && countFor(closeCounts, d) !== autoCountFor(d)) {
|
||
<span class="db-muted bd-denom__auto">system counted {{ autoCountFor(d) }}</span>
|
||
}
|
||
</td>
|
||
<td class="num">₹{{ subtotalFor(closeCounts, d) | number: '1.2-2' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (closeDenomTotal > 0) {
|
||
<p class="bd-hint">
|
||
Denomination total: <strong>₹{{ closeDenomTotal | number: '1.2-2' }}</strong>
|
||
@if (hasAutoFilledCounts) {
|
||
<button type="button" class="pg-btn bd-denom__reset" (click)="resetToAutoFill()">
|
||
Reset to auto-filled count
|
||
</button>
|
||
}
|
||
</p>
|
||
}
|
||
|
||
<!-- Shown for an unreconciled cashier too, not only a late close. The backend blocks a close
|
||
over a cashier who took cash on this date and never reconciled it, and accepts a reason as
|
||
the override — but this field only appeared for a late close, so a same-day close in that
|
||
state had nowhere to type the reason and could not be completed at all until the date had
|
||
passed. -->
|
||
@if (isLateClose || hasUnreconciledOrUnsettledCashiers) {
|
||
<label class="pg-field bd-panel__wide">
|
||
<span>
|
||
@if (isLateClose) {
|
||
Why was this day left open? (required)
|
||
} @else {
|
||
Why close over a cashier who has not reconciled? (required)
|
||
}
|
||
</span>
|
||
<input
|
||
type="text"
|
||
[(ngModel)]="forceReason"
|
||
placeholder="e.g. cashier on leave, cash not settled that evening"
|
||
/>
|
||
</label>
|
||
}
|
||
|
||
<label class="pg-field bd-panel__wide">
|
||
<span>Remarks (optional)</span>
|
||
<input type="text" [(ngModel)]="closeRemarks" placeholder="Anything worth recording about this day" />
|
||
</label>
|
||
|
||
<p class="bd-hint">
|
||
The figure above is stored against the record, so a difference stays readable later even if a transaction is
|
||
reversed afterwards.
|
||
</p>
|
||
|
||
@if (hasUnreconciledOrUnsettledCashiers) {
|
||
<div class="pg-alert pg-alert--error bd-reconcile-block">
|
||
<mat-icon>block</mat-icon>
|
||
<div>
|
||
<strong>Cannot close the day — cashiers still holding cash have not been reconciled or settled.</strong>
|
||
<ul class="bd-reconcile-block__list">
|
||
@for (detail of unreconciledOrUnsettledCashierDetails; track detail.name) {
|
||
<li>
|
||
<strong>{{ detail.name }}</strong> —
|
||
@if (detail.notReconciled && detail.notSettled) {
|
||
Not Reconciled, Not Settled
|
||
} @else if (detail.notReconciled) {
|
||
Not Reconciled
|
||
} @else {
|
||
Not Settled
|
||
}
|
||
</li>
|
||
}
|
||
</ul>
|
||
<span class="db-muted"
|
||
>Each cashier must reconcile their cash and settle it back to the vault before the branch day can be
|
||
closed.</span
|
||
>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<div class="db-panel__actions">
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="submitClose()" [disabled]="!canSubmitClose">
|
||
{{ isLateClose ? 'Force Close Day' : 'Close Day' }}
|
||
</button>
|
||
<button type="button" class="pg-btn" (click)="cancelClose()">Cancel</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
@if (loading) {
|
||
<div class="pg-empty">Loading…</div>
|
||
}
|
||
|
||
@if (!loading && loaded && days.length === 0 && !missedOnly) {
|
||
<div class="pg-empty">
|
||
<mat-icon>event_note</mat-icon>
|
||
<p>No business day has been opened for this branch yet.</p>
|
||
</div>
|
||
}
|
||
|
||
<!-- The register -->
|
||
@if (days.length > 0 && !missedOnly) {
|
||
<div class="pg-card">
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Business Date</th>
|
||
<th>Status</th>
|
||
<th>Opened By</th>
|
||
<th class="num">Opening ₹</th>
|
||
<th>Closed By</th>
|
||
<th class="num">Closing ₹</th>
|
||
<th class="num">Book ₹</th>
|
||
<th>Difference</th>
|
||
<th>Note</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (d of days; track d.id) {
|
||
<tr>
|
||
<td>
|
||
<span class="pg-chip">{{ d.businessDate }}</span>
|
||
</td>
|
||
<td>
|
||
@if (d.status === 'OPEN') {
|
||
<span class="db-badge db-badge--pending">OPEN</span>
|
||
} @else if (d.forceClosed) {
|
||
<span class="db-badge db-badge--rejected" [title]="d.forceCloseReason ?? ''">FORCE CLOSED</span>
|
||
} @else {
|
||
<span class="db-badge db-badge--ok">CLOSED</span>
|
||
}
|
||
</td>
|
||
<td>
|
||
{{ d.openedByName ?? '—' }}
|
||
<span class="db-muted">· {{ timeOnly(d.openedAt) }}</span>
|
||
</td>
|
||
<td class="num">₹{{ d.openingCash | number: '1.2-2' }}</td>
|
||
<td>
|
||
{{ d.closedByName ?? '—' }}
|
||
@if (d.closedAt) {
|
||
<span class="db-muted">· {{ timeOnly(d.closedAt) }}</span>
|
||
}
|
||
</td>
|
||
<td class="num">{{ d.closingCash != null ? '₹' + (d.closingCash | number: '1.2-2') : '—' }}</td>
|
||
<td class="num">{{ d.bookCash != null ? '₹' + (d.bookCash | number: '1.2-2') : '—' }}</td>
|
||
<td>
|
||
@if (d.cashDifference == null) {
|
||
—
|
||
} @else if (d.cashDifference === 0) {
|
||
<span class="db-badge db-badge--ok">Tallied</span>
|
||
} @else {
|
||
<span class="db-badge db-badge--rejected">{{ differenceLabel(d.cashDifference) }}</span>
|
||
}
|
||
</td>
|
||
<td class="bd-note">
|
||
@if (d.forceCloseReason) {
|
||
<strong>Left open:</strong> {{ d.forceCloseReason }}
|
||
}
|
||
@if (d.remarks) {
|
||
<span class="db-muted">{{ d.remarks }}</span>
|
||
}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
}
|
||
</div>
|