338 lines
13 KiB
HTML
338 lines
13 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" placeholder="Carried from last close" />
|
||
</label>
|
||
</div>
|
||
<p class="bd-hint">
|
||
Left blank, yesterday's closing figure carries forward automatically — which is what keeps the opening balance
|
||
from drifting.
|
||
</p>
|
||
<div class="db-panel__actions">
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="submitOpen()" [disabled]="opening || !openDate">
|
||
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--in">
|
||
<span>+ Cash drawn from vault</span>
|
||
<span class="bd-calc__amt">₹{{ preview.vaultIn | 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--out">
|
||
<span>− Cash settled back to vault</span>
|
||
<span class="bd-calc__amt">₹{{ preview.settledOut | 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>
|
||
</div>
|
||
}
|
||
|
||
<div class="db-panel__body">
|
||
<label class="pg-field">
|
||
<span>Physical cash counted</span>
|
||
<input type="number" step="0.01" [(ngModel)]="closingCash" 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">
|
||
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.
|
||
</p>
|
||
|
||
@if (isLateClose) {
|
||
<label class="pg-field bd-panel__wide">
|
||
<span>Why was this day left open? (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>
|
||
|
||
<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>
|