feat(cashier): add morning transfer & previous-day settlement hard block

- validateCashierHasMorningTransfer() blocks deposits & withdrawals when no today's cash transfer received
- validatePreviousDayClosed() blocks all transactions when previous day unsettled
- morningTransferReceived field added to cashier-day-status API response
- Warning banners shown in Savings, Current, FD, RD transaction forms
- Submit button disabled when either condition is active
- getCashierTotalCash SQL fixed: dm.denomination_value -> dm.denomination

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-01 13:14:29 +05:30
co-authored by Claude Sonnet 4.6
parent f291e4da5b
commit 4ffeb6ee92
13 changed files with 575 additions and 8 deletions
@@ -135,6 +135,40 @@
</div>
</div>
<!-- Previous-day settlement pending banner -->
@if (previousDayUnsettled) {
<div class="sat__day-close-banner sat__day-close-banner--withdrawal">
<mat-icon class="sat__day-close-ico">lock_clock</mat-icon>
<div class="sat__day-close-body">
<strong>Previous day not closed — {{ unsettledSince }}</strong>
<span>
Complete Cash Settlement for {{ unsettledSince }} before processing any transactions.
<b>All deposits and withdrawals are blocked.</b>
Pending cash: ₹{{ pendingAmount | number: '1.2-2' }}
</span>
</div>
<a class="sat__day-close-link" routerLink="/cashier/settlements">
<mat-icon>arrow_forward</mat-icon> Go to Settlement
</a>
</div>
}
<!-- Morning cash transfer not received banner -->
@if (!morningTransferReceived && !previousDayUnsettled) {
<div class="sat__day-close-banner sat__day-close-banner--withdrawal">
<mat-icon class="sat__day-close-ico">account_balance_wallet</mat-icon>
<div class="sat__day-close-body">
<strong>Morning cash transfer not received</strong>
<span>
No cash has been allocated to your drawer today. Request a morning transfer from the vault before processing
any transactions. <b>All deposits and withdrawals are blocked.</b>
</span>
</div>
<a class="sat__day-close-link" routerLink="/cashier/transfers">
<mat-icon>arrow_forward</mat-icon> Request Transfer
</a>
</div>
}
<form [formGroup]="currentAccountTransactionForm" (ngSubmit)="submit()" class="sat__form">
<!-- ─── LEFT COLUMN: denomination breakdown ────────────────────── -->
<section class="sat__panel sat__panel--denom">
@@ -514,7 +548,9 @@
[disabled]="
!currentAccountTransactionForm.valid ||
(denominationTotal > 0 && !denominationMatchesForm) ||
hasWithdrawalDenomOverdraft
hasWithdrawalDenomOverdraft ||
previousDayUnsettled ||
!morningTransferReceived
"
>
<fa-icon icon="check" class="m-r-6"></fa-icon>
@@ -1189,3 +1189,86 @@ $red: #b91c1c;
color: #b91c1c;
}
}
// ── Previous-day settlement pending banner ──
.sat__day-close-banner {
display: flex;
align-items: flex-start;
gap: 14px;
margin: 12px 20px 0;
padding: 14px 18px;
background: #fffbeb;
border: 1.5px solid #fcd34d;
border-left: 5px solid #f59e0b;
border-radius: 10px;
&--withdrawal {
background: #fff5f5;
border-color: #fca5a5;
border-left-color: #ef4444;
.sat__day-close-ico {
color: #ef4444;
}
.sat__day-close-link {
color: #b91c1c;
border-color: #fca5a5;
}
}
}
.sat__day-close-ico {
color: #b45309;
font-size: 22px;
height: 22px;
width: 22px;
flex-shrink: 0;
margin-top: 1px;
}
.sat__day-close-body {
flex: 1;
display: flex;
flex-direction: column;
gap: 3px;
strong {
font-size: 13px;
font-weight: 700;
color: #78350f;
}
span {
font-size: 12px;
color: #92400e;
line-height: 1.5;
}
b {
font-weight: 800;
color: #b91c1c;
}
}
.sat__day-close-link {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 5px 12px;
border-radius: 999px;
border: 1.5px solid #fcd34d;
background: #fff;
color: #92400e;
font-size: 12px;
font-weight: 700;
white-space: nowrap;
text-decoration: none;
flex-shrink: 0;
mat-icon {
font-size: 14px;
height: 14px;
width: 14px;
}
}
@@ -120,6 +120,11 @@ export class CurrentAccountTransactionsComponent implements OnInit {
openingTotal = 0;
openingLoaded = false;
previousDayUnsettled = false;
morningTransferReceived = true;
unsettledSince = '';
pendingAmount = 0;
get openingNotes(): any[] {
return this.openingDenoms.filter((d) => d.kind !== 'coin');
}
@@ -151,6 +156,7 @@ export class CurrentAccountTransactionsComponent implements OnInit {
this.loadDenominations();
this.loadContextStrip();
this.loadOpeningPosition();
this.loadDayStatus();
}
/** Load active denominations from the Denomination Master so the cash
@@ -203,6 +209,21 @@ export class CurrentAccountTransactionsComponent implements OnInit {
});
}
private loadDayStatus(): void {
this.cashierService.getCashierDayStatus().subscribe({
next: (res: any) => {
this.previousDayUnsettled = res?.previousDayUnsettled ?? false;
this.morningTransferReceived = res?.morningTransferReceived ?? true;
this.unsettledSince = res?.unsettledSince ?? '';
this.pendingAmount = Number(res?.pendingAmount ?? 0);
},
error: () => {
this.previousDayUnsettled = false;
this.morningTransferReceived = true;
}
});
}
private loadOpeningPosition(): void {
const today = new Date();
const asOfDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
@@ -390,7 +411,7 @@ export class CurrentAccountTransactionsComponent implements OnInit {
}
get withdrawalDenomOverdraft(): Array<{ label: string; available: number; requested: number }> {
if (!this.transactionType.withdrawal || !this.openingLoaded || this.openingDenoms.length === 0) return [];
if (!this.transactionType.withdrawal || !this.openingLoaded) return [];
const openMap = new Map<string, number>();
for (const d of this.openingDenoms) {
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));