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:
co-authored by
Claude Sonnet 4.6
parent
f291e4da5b
commit
4ffeb6ee92
@@ -125,6 +125,9 @@ export class CashierService {
|
||||
if (asOfDate) params = params.set('asOfDate', asOfDate);
|
||||
return this.http.get('/cashreports/cashier-position/me', { params });
|
||||
}
|
||||
getCashierDayStatus(): Observable<any> {
|
||||
return this.http.get('/cashreports/cashier-day-status');
|
||||
}
|
||||
|
||||
checkSettlement(cashierId: number, date: string): Observable<any> {
|
||||
const params = new HttpParams().set('cashierId', String(cashierId)).set('date', date);
|
||||
|
||||
+37
-1
@@ -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>
|
||||
|
||||
+83
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -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));
|
||||
|
||||
+37
-1
@@ -109,6 +109,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]="accountTransactionForm" (ngSubmit)="submit()" class="sat__form">
|
||||
<!-- LEFT: denomination breakdown -->
|
||||
<section class="sat__panel sat__panel--denom">
|
||||
@@ -480,7 +514,9 @@
|
||||
[disabled]="
|
||||
!accountTransactionForm.valid ||
|
||||
(denominationTotal > 0 && !denominationMatchesForm) ||
|
||||
hasWithdrawalDenomOverdraft
|
||||
hasWithdrawalDenomOverdraft ||
|
||||
previousDayUnsettled ||
|
||||
!morningTransferReceived
|
||||
"
|
||||
>
|
||||
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||
|
||||
+83
@@ -1101,3 +1101,86 @@ $amber-d: #b45309;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -95,6 +95,11 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
|
||||
openingTotal = 0;
|
||||
openingLoaded = false;
|
||||
|
||||
previousDayUnsettled = false;
|
||||
morningTransferReceived = true;
|
||||
unsettledSince = '';
|
||||
pendingAmount = 0;
|
||||
|
||||
get openingNotes(): any[] {
|
||||
return this.openingDenoms.filter((d) => d.kind !== 'coin');
|
||||
}
|
||||
@@ -130,6 +135,7 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
|
||||
this.loadDenominations();
|
||||
this.loadContextStrip();
|
||||
this.loadOpeningPosition();
|
||||
this.loadDayStatus();
|
||||
}
|
||||
|
||||
/** Load active denominations from the Denomination Master so the cash
|
||||
@@ -180,6 +186,21 @@ export class FixedDepositsCashTransactionComponent 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')}`;
|
||||
@@ -313,7 +334,7 @@ export class FixedDepositsCashTransactionComponent 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));
|
||||
|
||||
+37
-1
@@ -109,6 +109,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]="depositRecurringDepositForm" (ngSubmit)="submit()" class="sat__form">
|
||||
<!-- LEFT: denomination breakdown -->
|
||||
<section class="sat__panel sat__panel--denom">
|
||||
@@ -486,7 +520,9 @@
|
||||
[disabled]="
|
||||
!depositRecurringDepositForm.valid ||
|
||||
(denominationTotal > 0 && !denominationMatchesForm) ||
|
||||
hasWithdrawalDenomOverdraft
|
||||
hasWithdrawalDenomOverdraft ||
|
||||
previousDayUnsettled ||
|
||||
!morningTransferReceived
|
||||
"
|
||||
>
|
||||
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||
|
||||
+83
@@ -1101,3 +1101,86 @@ $amber-d: #b45309;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -104,6 +104,11 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
||||
|
||||
openingDenoms: any[] = [];
|
||||
openingTotal = 0;
|
||||
|
||||
previousDayUnsettled = false;
|
||||
morningTransferReceived = true;
|
||||
unsettledSince = '';
|
||||
pendingAmount = 0;
|
||||
openingLoaded = false;
|
||||
|
||||
get openingNotes(): any[] {
|
||||
@@ -148,6 +153,7 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
||||
this.loadDenominations();
|
||||
this.loadContextStrip();
|
||||
this.loadOpeningPosition();
|
||||
this.loadDayStatus();
|
||||
}
|
||||
|
||||
/** Load active denominations from the Denomination Master so the cash
|
||||
@@ -198,6 +204,21 @@ export class DepositRecurringDepositsAccountComponent 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')}`;
|
||||
@@ -334,7 +355,7 @@ export class DepositRecurringDepositsAccountComponent 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));
|
||||
|
||||
+37
-1
@@ -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]="savingAccountTransactionForm" (ngSubmit)="submit()" class="sat__form">
|
||||
<!-- ─── LEFT COLUMN: denomination breakdown ────────────────────── -->
|
||||
<section class="sat__panel sat__panel--denom">
|
||||
@@ -514,7 +548,9 @@
|
||||
[disabled]="
|
||||
!savingAccountTransactionForm.valid ||
|
||||
(denominationTotal > 0 && !denominationMatchesForm) ||
|
||||
hasWithdrawalDenomOverdraft
|
||||
hasWithdrawalDenomOverdraft ||
|
||||
previousDayUnsettled ||
|
||||
!morningTransferReceived
|
||||
"
|
||||
>
|
||||
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||
|
||||
+83
@@ -1216,3 +1216,86 @@ $red: #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;
|
||||
}
|
||||
}
|
||||
|
||||
+26
-1
@@ -140,6 +140,15 @@ export class SavingsAccountTransactionsComponent implements OnInit {
|
||||
/** True once the opening-position API has responded (success or error). */
|
||||
openingLoaded = false;
|
||||
|
||||
/** True when cashier has an unsettled previous business day. */
|
||||
previousDayUnsettled = false;
|
||||
/** False when cashier has not received today's morning cash transfer. */
|
||||
morningTransferReceived = true;
|
||||
/** Date string of the earliest unsettled transfer (yyyy-MM-dd). */
|
||||
unsettledSince = '';
|
||||
/** Pending cash amount from previous day. */
|
||||
pendingAmount = 0;
|
||||
|
||||
get openingNotes(): any[] {
|
||||
return this.openingDenoms.filter((d) => d.kind !== 'coin');
|
||||
}
|
||||
@@ -171,6 +180,7 @@ export class SavingsAccountTransactionsComponent implements OnInit {
|
||||
this.loadDenominations();
|
||||
this.loadContextStrip();
|
||||
this.loadOpeningPosition();
|
||||
this.loadDayStatus();
|
||||
}
|
||||
|
||||
/** Load active denominations from the Denomination Master so the cash
|
||||
@@ -231,6 +241,21 @@ export class SavingsAccountTransactionsComponent 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')}`;
|
||||
@@ -457,7 +482,7 @@ export class SavingsAccountTransactionsComponent implements OnInit {
|
||||
|
||||
/** Denominations where cashier is giving more than currently available (withdrawal only). */
|
||||
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));
|
||||
|
||||
Reference in New Issue
Block a user