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);
|
if (asOfDate) params = params.set('asOfDate', asOfDate);
|
||||||
return this.http.get('/cashreports/cashier-position/me', { params });
|
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> {
|
checkSettlement(cashierId: number, date: string): Observable<any> {
|
||||||
const params = new HttpParams().set('cashierId', String(cashierId)).set('date', date);
|
const params = new HttpParams().set('cashierId', String(cashierId)).set('date', date);
|
||||||
|
|||||||
+37
-1
@@ -135,6 +135,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<form [formGroup]="currentAccountTransactionForm" (ngSubmit)="submit()" class="sat__form">
|
||||||
<!-- ─── LEFT COLUMN: denomination breakdown ────────────────────── -->
|
<!-- ─── LEFT COLUMN: denomination breakdown ────────────────────── -->
|
||||||
<section class="sat__panel sat__panel--denom">
|
<section class="sat__panel sat__panel--denom">
|
||||||
@@ -514,7 +548,9 @@
|
|||||||
[disabled]="
|
[disabled]="
|
||||||
!currentAccountTransactionForm.valid ||
|
!currentAccountTransactionForm.valid ||
|
||||||
(denominationTotal > 0 && !denominationMatchesForm) ||
|
(denominationTotal > 0 && !denominationMatchesForm) ||
|
||||||
hasWithdrawalDenomOverdraft
|
hasWithdrawalDenomOverdraft ||
|
||||||
|
previousDayUnsettled ||
|
||||||
|
!morningTransferReceived
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||||
|
|||||||
+83
@@ -1189,3 +1189,86 @@ $red: #b91c1c;
|
|||||||
color: #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;
|
openingTotal = 0;
|
||||||
openingLoaded = false;
|
openingLoaded = false;
|
||||||
|
|
||||||
|
previousDayUnsettled = false;
|
||||||
|
morningTransferReceived = true;
|
||||||
|
unsettledSince = '';
|
||||||
|
pendingAmount = 0;
|
||||||
|
|
||||||
get openingNotes(): any[] {
|
get openingNotes(): any[] {
|
||||||
return this.openingDenoms.filter((d) => d.kind !== 'coin');
|
return this.openingDenoms.filter((d) => d.kind !== 'coin');
|
||||||
}
|
}
|
||||||
@@ -151,6 +156,7 @@ export class CurrentAccountTransactionsComponent implements OnInit {
|
|||||||
this.loadDenominations();
|
this.loadDenominations();
|
||||||
this.loadContextStrip();
|
this.loadContextStrip();
|
||||||
this.loadOpeningPosition();
|
this.loadOpeningPosition();
|
||||||
|
this.loadDayStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load active denominations from the Denomination Master so the cash
|
/** 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 {
|
private loadOpeningPosition(): void {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const asOfDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
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 }> {
|
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>();
|
const openMap = new Map<string, number>();
|
||||||
for (const d of this.openingDenoms) {
|
for (const d of this.openingDenoms) {
|
||||||
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));
|
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));
|
||||||
|
|||||||
+37
-1
@@ -109,6 +109,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<form [formGroup]="accountTransactionForm" (ngSubmit)="submit()" class="sat__form">
|
||||||
<!-- LEFT: denomination breakdown -->
|
<!-- LEFT: denomination breakdown -->
|
||||||
<section class="sat__panel sat__panel--denom">
|
<section class="sat__panel sat__panel--denom">
|
||||||
@@ -480,7 +514,9 @@
|
|||||||
[disabled]="
|
[disabled]="
|
||||||
!accountTransactionForm.valid ||
|
!accountTransactionForm.valid ||
|
||||||
(denominationTotal > 0 && !denominationMatchesForm) ||
|
(denominationTotal > 0 && !denominationMatchesForm) ||
|
||||||
hasWithdrawalDenomOverdraft
|
hasWithdrawalDenomOverdraft ||
|
||||||
|
previousDayUnsettled ||
|
||||||
|
!morningTransferReceived
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||||
|
|||||||
+83
@@ -1101,3 +1101,86 @@ $amber-d: #b45309;
|
|||||||
color: #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
@@ -95,6 +95,11 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
|
|||||||
openingTotal = 0;
|
openingTotal = 0;
|
||||||
openingLoaded = false;
|
openingLoaded = false;
|
||||||
|
|
||||||
|
previousDayUnsettled = false;
|
||||||
|
morningTransferReceived = true;
|
||||||
|
unsettledSince = '';
|
||||||
|
pendingAmount = 0;
|
||||||
|
|
||||||
get openingNotes(): any[] {
|
get openingNotes(): any[] {
|
||||||
return this.openingDenoms.filter((d) => d.kind !== 'coin');
|
return this.openingDenoms.filter((d) => d.kind !== 'coin');
|
||||||
}
|
}
|
||||||
@@ -130,6 +135,7 @@ export class FixedDepositsCashTransactionComponent implements OnInit {
|
|||||||
this.loadDenominations();
|
this.loadDenominations();
|
||||||
this.loadContextStrip();
|
this.loadContextStrip();
|
||||||
this.loadOpeningPosition();
|
this.loadOpeningPosition();
|
||||||
|
this.loadDayStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load active denominations from the Denomination Master so the cash
|
/** 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 {
|
private loadOpeningPosition(): void {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const asOfDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
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 }> {
|
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>();
|
const openMap = new Map<string, number>();
|
||||||
for (const d of this.openingDenoms) {
|
for (const d of this.openingDenoms) {
|
||||||
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));
|
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));
|
||||||
|
|||||||
+37
-1
@@ -109,6 +109,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<form [formGroup]="depositRecurringDepositForm" (ngSubmit)="submit()" class="sat__form">
|
||||||
<!-- LEFT: denomination breakdown -->
|
<!-- LEFT: denomination breakdown -->
|
||||||
<section class="sat__panel sat__panel--denom">
|
<section class="sat__panel sat__panel--denom">
|
||||||
@@ -486,7 +520,9 @@
|
|||||||
[disabled]="
|
[disabled]="
|
||||||
!depositRecurringDepositForm.valid ||
|
!depositRecurringDepositForm.valid ||
|
||||||
(denominationTotal > 0 && !denominationMatchesForm) ||
|
(denominationTotal > 0 && !denominationMatchesForm) ||
|
||||||
hasWithdrawalDenomOverdraft
|
hasWithdrawalDenomOverdraft ||
|
||||||
|
previousDayUnsettled ||
|
||||||
|
!morningTransferReceived
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
||||||
|
|||||||
+83
@@ -1101,3 +1101,86 @@ $amber-d: #b45309;
|
|||||||
color: #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
@@ -104,6 +104,11 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
|||||||
|
|
||||||
openingDenoms: any[] = [];
|
openingDenoms: any[] = [];
|
||||||
openingTotal = 0;
|
openingTotal = 0;
|
||||||
|
|
||||||
|
previousDayUnsettled = false;
|
||||||
|
morningTransferReceived = true;
|
||||||
|
unsettledSince = '';
|
||||||
|
pendingAmount = 0;
|
||||||
openingLoaded = false;
|
openingLoaded = false;
|
||||||
|
|
||||||
get openingNotes(): any[] {
|
get openingNotes(): any[] {
|
||||||
@@ -148,6 +153,7 @@ export class DepositRecurringDepositsAccountComponent implements OnInit {
|
|||||||
this.loadDenominations();
|
this.loadDenominations();
|
||||||
this.loadContextStrip();
|
this.loadContextStrip();
|
||||||
this.loadOpeningPosition();
|
this.loadOpeningPosition();
|
||||||
|
this.loadDayStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load active denominations from the Denomination Master so the cash
|
/** 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 {
|
private loadOpeningPosition(): void {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const asOfDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
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 }> {
|
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>();
|
const openMap = new Map<string, number>();
|
||||||
for (const d of this.openingDenoms) {
|
for (const d of this.openingDenoms) {
|
||||||
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));
|
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));
|
||||||
|
|||||||
+37
-1
@@ -135,6 +135,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<form [formGroup]="savingAccountTransactionForm" (ngSubmit)="submit()" class="sat__form">
|
||||||
<!-- ─── LEFT COLUMN: denomination breakdown ────────────────────── -->
|
<!-- ─── LEFT COLUMN: denomination breakdown ────────────────────── -->
|
||||||
<section class="sat__panel sat__panel--denom">
|
<section class="sat__panel sat__panel--denom">
|
||||||
@@ -514,7 +548,9 @@
|
|||||||
[disabled]="
|
[disabled]="
|
||||||
!savingAccountTransactionForm.valid ||
|
!savingAccountTransactionForm.valid ||
|
||||||
(denominationTotal > 0 && !denominationMatchesForm) ||
|
(denominationTotal > 0 && !denominationMatchesForm) ||
|
||||||
hasWithdrawalDenomOverdraft
|
hasWithdrawalDenomOverdraft ||
|
||||||
|
previousDayUnsettled ||
|
||||||
|
!morningTransferReceived
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<fa-icon icon="check" class="m-r-6"></fa-icon>
|
<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). */
|
/** True once the opening-position API has responded (success or error). */
|
||||||
openingLoaded = false;
|
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[] {
|
get openingNotes(): any[] {
|
||||||
return this.openingDenoms.filter((d) => d.kind !== 'coin');
|
return this.openingDenoms.filter((d) => d.kind !== 'coin');
|
||||||
}
|
}
|
||||||
@@ -171,6 +180,7 @@ export class SavingsAccountTransactionsComponent implements OnInit {
|
|||||||
this.loadDenominations();
|
this.loadDenominations();
|
||||||
this.loadContextStrip();
|
this.loadContextStrip();
|
||||||
this.loadOpeningPosition();
|
this.loadOpeningPosition();
|
||||||
|
this.loadDayStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load active denominations from the Denomination Master so the cash
|
/** 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 {
|
private loadOpeningPosition(): void {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const asOfDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
|
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). */
|
/** Denominations where cashier is giving more than currently available (withdrawal only). */
|
||||||
get withdrawalDenomOverdraft(): Array<{ label: string; available: number; requested: number }> {
|
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>();
|
const openMap = new Map<string, number>();
|
||||||
for (const d of this.openingDenoms) {
|
for (const d of this.openingDenoms) {
|
||||||
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));
|
openMap.set(`${d.kind}:${d.denominationValue}`, Number(d.count ?? 0));
|
||||||
|
|||||||
Reference in New Issue
Block a user