diff --git a/src/app/cashier/branch-day/branch-day.component.html b/src/app/cashier/branch-day/branch-day.component.html index 9b97f565d..25bbb9933 100644 --- a/src/app/cashier/branch-day/branch-day.component.html +++ b/src/app/cashier/branch-day/branch-day.component.html @@ -135,11 +135,32 @@

- 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. + @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. + }

+ @if (hasAutoFilledOpenCounts && openLiveDifference !== null) { + @if (openLiveDifference === 0) { +
+ vs. yesterday's close + Tallied +
+ } @else { +
+ vs. yesterday's close + + {{ openLiveDifference > 0 ? 'Excess' : 'Short' }} ₹{{ absValue(openLiveDifference) | number: '1.2-2' }} + +
+ } + } +
@@ -160,7 +181,7 @@ - @@ -191,7 +215,7 @@ - @@ -218,11 +245,27 @@ @if (openDenomTotal > 0) {

Denomination total: ₹{{ openDenomTotal | number: '1.2-2' }} + @if (hasAutoFilledOpenCounts) { + + }

} + @if (openLiveDifference !== null && openLiveDifference !== 0) { + + } +
- @@ -414,9 +457,14 @@

- 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. + @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. + }

@@ -439,7 +487,7 @@
- @@ -470,7 +521,7 @@ - @@ -497,6 +551,11 @@ @if (closeDenomTotal > 0) {

Denomination total: ₹{{ closeDenomTotal | number: '1.2-2' }} + @if (hasAutoFilledCounts) { + + }

} diff --git a/src/app/cashier/branch-day/branch-day.component.scss b/src/app/cashier/branch-day/branch-day.component.scss index 334ee94ba..a4b1ac062 100644 --- a/src/app/cashier/branch-day/branch-day.component.scss +++ b/src/app/cashier/branch-day/branch-day.component.scss @@ -184,6 +184,21 @@ .bd-denom { margin: 4px 0 0; max-width: 520px; + + /* Flags a row where the hand-count no longer matches what the vault/cashier positions reported. */ + &__auto { + display: block; + margin-top: 2px; + font-size: 10px; + white-space: nowrap; + } + + &__reset { + margin-left: 10px; + height: 24px; + padding: 0 10px; + font-size: 11px; + } } /* Auto-fetched EOD summary: what the vault and every cashier under it currently hold. */ diff --git a/src/app/cashier/branch-day/branch-day.component.ts b/src/app/cashier/branch-day/branch-day.component.ts index f6eb1cc20..9abba824f 100644 --- a/src/app/cashier/branch-day/branch-day.component.ts +++ b/src/app/cashier/branch-day/branch-day.component.ts @@ -90,6 +90,9 @@ export class BranchDayComponent implements OnInit { showOpenForm = false; openDate = ''; openingCash: number | null = null; + /** Required only when the counted/typed opening figure differs from yesterday's own closing + * figure — the backend rejects the open without it in that case. */ + openDifferenceReason = ''; opening = false; // Close-day form. The date is fixed by which day is being closed, never typed. @@ -106,6 +109,15 @@ export class BranchDayComponent implements OnInit { coins: Denom[] = []; openCounts: Record = {}; closeCounts: Record = {}; + /** What yesterday's own close counted, note by note — the open form starts from this instead of + * zero. The backend already carries this figure forward on its own when nothing is submitted for + * the day being opened; showing it here is only about letting whoever opens the day see and + * correct it, not a second place the carry-forward logic lives. */ + openCountsAuto: Record = {}; + /** What the vault + every cashier's live position added up to when the close form opened — the + * physical-count table starts from this instead of zero, since the EOD summary already fetched + * it. Kept separately from closeCounts so a manual recount can be told apart from it and undone. */ + closeCountsAuto: Record = {}; get allDenoms(): Denom[] { return [ @@ -212,21 +224,98 @@ export class BranchDayComponent implements OnInit { // over it is how a carried balance drifts. this.openingCash = null; this.openCounts = {}; + this.openCountsAuto = {}; + this.openDifferenceReason = ''; this.successMsg = ''; this.errorMsg = ''; + this.loadCarriedForwardDenominations(); } cancelOpen(): void { this.showOpenForm = false; + this.openCountsAuto = {}; + } + + /** + * Pulls yesterday's own closing note/coin count so the open form shows what is about to be + * carried forward instead of a blank table the caller has to trust blindly. Only the most + * recently closed day is used — the same day the backend itself falls back to when the caller + * submits no denominations and no lump sum. + */ + private loadCarriedForwardDenominations(): void { + const lastClosed = this.days.find((d) => d.status === 'CLOSED' && d.closingCash != null); + if (!lastClosed) return; + this.service + .branchDayDenominations(lastClosed.officeId, lastClosed.businessDate) + .pipe(catchError(() => of(null))) + .subscribe((res: any) => { + const closing = res?.closing || []; + if (!closing.length) return; + const totals: Record = {}; + for (const d of closing) { + totals[d.denominationId] = (totals[d.denominationId] || 0) + Number(d.count || 0); + } + this.openCountsAuto = totals; + // Only overwrite the table if nothing has been typed/counted since the form opened. + if (this.openDenomTotal === 0 && this.openingCash == null) { + this.openCounts = { ...totals }; + } + }); + } + + get hasAutoFilledOpenCounts(): boolean { + return Object.keys(this.openCountsAuto).length > 0; + } + + autoOpenCountFor(d: Denom): number { + return this.openCountsAuto[d.id] || 0; + } + + resetOpenToAutoFill(): void { + this.openCounts = { ...this.openCountsAuto }; + } + + get carriedForwardTotal(): number { + return this.totalOf(this.openCountsAuto); + } + + /** The figure the form would actually submit right now — the denomination total when any note/coin + * has been counted, otherwise the plain typed-in amount. Mirrors {@link effectiveClosingFigure}. */ + get effectiveOpeningFigure(): number | null { + return this.openDenomTotal > 0 ? this.openDenomTotal : this.openingCash; + } + + /** Difference between what is about to be opened with and what yesterday actually closed with — + * visible before saving, the same way the close form shows a live difference against the book + * figure, so a short/excess opening count isn't discovered only after the day is already running. */ + get openLiveDifference(): number | null { + if (!this.hasAutoFilledOpenCounts || this.effectiveOpeningFigure == null) return null; + return this.effectiveOpeningFigure - this.carriedForwardTotal; + } + + /** True once a reason is needed but hasn't been typed — the same "explain the gap before it's + * allowed" gate the close form already applies to a late close. */ + get needsOpenDifferenceReason(): boolean { + return this.openLiveDifference != null && this.openLiveDifference !== 0 && !this.openDifferenceReason.trim(); + } + + get canSubmitOpen(): boolean { + return !!this.openDate && !this.opening && !this.needsOpenDifferenceReason; } submitOpen(): void { - if (!this.openDate) return; + if (!this.openDate || this.needsOpenDifferenceReason) return; this.opening = true; this.errorMsg = ''; const denominations = this.denominationPayload(this.openCounts); this.service - .openBranchDay(this.openDate, this.openingCash ?? undefined, undefined, denominations) + .openBranchDay( + this.openDate, + this.openingCash ?? undefined, + undefined, + denominations, + this.openDifferenceReason.trim() || undefined + ) .pipe( catchError((err: any) => { this.errorMsg = @@ -252,6 +341,7 @@ export class BranchDayComponent implements OnInit { this.closeTarget = day; this.closingCash = null; this.closeCounts = {}; + this.closeCountsAuto = {}; this.closeRemarks = ''; this.forceReason = ''; this.successMsg = ''; @@ -327,9 +417,47 @@ export class BranchDayComponent implements OnInit { .subscribe((results: any[]) => { this.eodLoading = false; this.eodTellers = results; + this.autoFillCloseCounts(); }); } + /** + * Seeds the physical-count table from what the vault and every cashier are already shown to be + * holding, instead of leaving it at zero and making whoever closes the day re-key numbers that + * are already on screen above. It stays a starting point, not a lock — any row can still be + * corrected against what was actually counted, and {@link resetToAutoFill} undoes a correction. + */ + private autoFillCloseCounts(): void { + const totals: Record = {}; + for (const t of this.eodTellers) { + for (const d of t.vault?.denominations || []) { + totals[d.denominationId] = (totals[d.denominationId] || 0) + Number(d.count || 0); + } + for (const c of t.cashiers || []) { + for (const d of c.denominations || []) { + totals[d.denominationId] = (totals[d.denominationId] || 0) + Number(d.count || 0); + } + } + } + if (Object.keys(totals).length === 0) return; + this.closeCountsAuto = totals; + this.closeCounts = { ...totals }; + } + + get hasAutoFilledCounts(): boolean { + return Object.keys(this.closeCountsAuto).length > 0; + } + + /** The auto-fetched count for one denomination, so a row that was hand-edited away from it can + * still show what the vault + cashier positions said. */ + autoCountFor(d: Denom): number { + return this.closeCountsAuto[d.id] || 0; + } + + resetToAutoFill(): void { + this.closeCounts = { ...this.closeCountsAuto }; + } + private loadTellerSummary(teller: any, date: string) { return forkJoin({ vault: this.service.vaultPosition(teller.id, date).pipe(catchError(() => of(null))), @@ -403,6 +531,7 @@ export class BranchDayComponent implements OnInit { cancelClose(): void { this.closeTarget = null; this.eodTellers = []; + this.closeCountsAuto = {}; } /** A date earlier than today is a late close, and has to say why it was left open. */ diff --git a/src/app/cashier/cashier-shared.scss b/src/app/cashier/cashier-shared.scss index 6985025e4..90f9984cc 100644 --- a/src/app/cashier/cashier-shared.scss +++ b/src/app/cashier/cashier-shared.scss @@ -206,6 +206,7 @@ border-radius: 6px; font-size: 12px; font-weight: 700; + white-space: nowrap; background: linear-gradient(135deg, #ecfeff, #cffafe); color: #155e75; border: 1px solid #a5f3fc; diff --git a/src/app/cashier/cashier.service.ts b/src/app/cashier/cashier.service.ts index 7725e5e36..65e4cce0b 100644 --- a/src/app/cashier/cashier.service.ts +++ b/src/app/cashier/cashier.service.ts @@ -229,17 +229,20 @@ export class CashierService { return this.http.get('/branchday/close-preview', { params }); } + /** openingDifferenceReason is required when the opening figure differs from yesterday's closing. */ openBranchDay( businessDate: string, openingCash?: number, officeId?: number, - denominations?: { denominationId: number; denominationValue: number; count: number }[] + denominations?: { denominationId: number; denominationValue: number; count: number }[], + openingDifferenceReason?: string ): Observable { return this.http.post('/branchday/open', { businessDate, openingCash: openingCash != null ? String(openingCash) : null, officeId: officeId ?? null, denominations: denominations?.length ? denominations : null, + openingDifferenceReason: openingDifferenceReason ?? null, locale: 'en' }); }
{{ d.label }} +
+ @if (hasAutoFilledOpenCounts && countFor(openCounts, d) !== autoOpenCountFor(d)) { + yesterday closed {{ autoOpenCountFor(d) }} + }
₹{{ subtotalFor(openCounts, d) | number: '1.2-2' }}
{{ d.label }} +
+ @if (hasAutoFilledOpenCounts && countFor(openCounts, d) !== autoOpenCountFor(d)) { + yesterday closed {{ autoOpenCountFor(d) }} + }
₹{{ subtotalFor(openCounts, d) | number: '1.2-2' }}
{{ d.label }} +
+ @if (hasAutoFilledCounts && countFor(closeCounts, d) !== autoCountFor(d)) { + system counted {{ autoCountFor(d) }} + }
₹{{ subtotalFor(closeCounts, d) | number: '1.2-2' }}
{{ d.label }} +
+ @if (hasAutoFilledCounts && countFor(closeCounts, d) !== autoCountFor(d)) { + system counted {{ autoCountFor(d) }} + }
₹{{ subtotalFor(closeCounts, d) | number: '1.2-2' }}