feat(cashier): complete maker-checker withdrawal disbursement flow

- Clerk manually enters token no with today-date unique validation (backend enforced)
- Cashier token lookup: auto-fill amount/account, lock amount field, status-specific errors
- Confirm dialog shows CASH DISBURSEMENT (blue) with no balance re-deduction warning
- Disbursement denomination saved to m_savings_txn_denomination for vault position update
- Block direct cashier withdrawal without approved maker-checker token
- DISBURSED status shown in My Requests and Checker inbox views with cashier/time info
- Remove getNextToken() service method and m_daily_token_counter table (0326 migration)
- Race condition protection: UPDATE WHERE status=APPROVED prevents double disbursement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-11 15:57:33 +05:30
co-authored by Claude Sonnet 4.6
parent de420bb8ab
commit 61cb09f352
26 changed files with 4822 additions and 248 deletions
@@ -473,6 +473,161 @@ $ws-fld-bd: #c8d8eb;
cursor: not-allowed;
}
}
&--reject {
background: #fff;
color: #dc2626;
font-weight: 700;
padding: 6px 16px;
border: 1.5px solid #fca5a5;
&:hover:not(:disabled) {
background: #fef2f2;
border-color: #dc2626;
}
&:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
}
// ── Clerk edit: rejection banner ──────────────────────────────────────────────
.te-reject-banner {
display: flex;
align-items: flex-start;
gap: 10px;
margin: 10px 12px 0;
padding: 12px 16px;
background: #fef2f2;
border: 1px solid #fca5a5;
border-left: 4px solid #dc2626;
border-radius: 8px;
color: #991b1b;
svg {
flex-shrink: 0;
margin-top: 1px;
color: #dc2626;
}
div {
display: flex;
flex-direction: column;
gap: 2px;
}
strong {
font-size: 13px;
font-weight: 700;
}
span {
font-size: 12.5px;
}
small {
font-size: 11.5px;
color: #b45309;
margin-top: 2px;
}
}
// ── Manager review extras ─────────────────────────────────────────────────────
// Fieldset wrapper around the row form — disabled in review mode to make it read-only,
// while staying layout-neutral so the cards render exactly like the clerk view.
.ws-rows-fieldset {
display: contents;
border: 0;
margin: 0;
padding: 0;
min-width: 0;
&[disabled] {
.ws-card {
cursor: default;
}
// Keep values fully legible (browsers fade disabled controls by default)
.ws-f__ctl,
.ws-amt-input,
input,
select,
textarea {
opacity: 1;
color: #1e293b;
-webkit-text-fill-color: #1e293b;
background: #f8fafc;
cursor: default;
}
.ws-seg__btn {
opacity: 1;
}
}
}
.ws-review-status {
display: inline-flex;
align-items: center;
padding: 4px 12px;
border-radius: 999px;
font-size: 11px;
font-weight: 800;
letter-spacing: 0.05em;
margin-left: 10px;
&[data-status='PENDING'] {
background: #fef9c3;
color: #a16207;
}
&[data-status='APPROVED'] {
background: #dcfce7;
color: #15803d;
}
&[data-status='REJECTED'] {
background: #fee2e2;
color: #b91c1c;
}
}
.ws-reject-hint {
margin: 0 0 10px;
font-size: 12.5px;
color: #64748b;
}
.ws-reject-textarea {
width: 100%;
border: 1.5px solid #e2e8f0;
border-radius: 10px;
padding: 10px 12px;
font-family: inherit;
font-size: 13px;
resize: vertical;
&:focus {
outline: none;
border-color: #dc2626;
}
}
.te-btn--danger {
background: #dc2626;
color: #fff;
border-color: #dc2626;
&:hover:not(:disabled) {
background: #b91c1c;
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
}
}
// ── Row cards ────────────────────────────────────────────────────────────────
@@ -736,6 +891,35 @@ select.ws-f__ctl {
cursor: pointer;
}
input.ws-f__ctl--readonly {
background: #f1f5f9;
color: #475569;
cursor: default;
user-select: none;
letter-spacing: 0.04em;
font-family: 'Courier New', monospace;
font-size: 11px;
}
input.ws-amt-input--locked {
background: #fef9c3;
color: #713f12;
cursor: not-allowed;
font-weight: 700;
}
.ws-amt-wrap--locked {
border-color: #fde047 !important;
background: #fefce8;
}
.ws-amt-locked-badge {
font-size: 10px;
margin-left: 3px;
vertical-align: middle;
cursor: help;
}
// ── Amount field ──────────────────────────────────────────────────────────────
.ws-amt-wrap {
display: flex;
@@ -765,7 +949,6 @@ select.ws-f__ctl {
.ws-amt-input {
color: #15803d;
font-weight: 700;
cursor: not-allowed;
}
}
}
@@ -1591,13 +1774,12 @@ select.te-cell {
background: #fff7ed !important;
}
// Cash payment: amount auto-filled from denomination (disabled style)
// Cash payment: amount highlighted style
.te-cell--cash-auto {
background: #f0fdf4 !important;
border-color: #86efac !important;
color: #15803d !important;
font-weight: 700 !important;
cursor: not-allowed;
}
// Denomination stock overdraft badge (closing position header)
@@ -2089,6 +2271,10 @@ select.te-cell {
&--withdrawal {
background: linear-gradient(135deg, #dc2626, #b91c1c);
}
&--disbursement {
background: linear-gradient(135deg, #0284c7, #0369a1);
}
}
.te-confirm-icon {
@@ -2140,6 +2326,25 @@ select.te-cell {
margin: 6px 0;
}
.te-confirm-disbursed-note {
justify-content: center;
border-bottom: none;
margin-top: 4px;
}
.te-confirm-disbursed-badge {
display: inline-block;
background: #dcfce7;
color: #166534;
border: 1px solid #86efac;
border-radius: 6px;
padding: 6px 12px;
font-size: 11.5px;
font-weight: 600;
text-align: center;
width: 100%;
}
.te-confirm-actions {
display: flex;
justify-content: flex-end;
@@ -2829,3 +3034,104 @@ select.te-cell {
}
}
}
// ── Approved Withdrawal Banner ────────────────────────────────────────────────
.ws-withdrawal-banner {
display: flex;
align-items: flex-start;
gap: 12px;
margin: 10px 16px;
padding: 12px 14px;
background: linear-gradient(135deg, #ecfdf5, #f0fdf4);
border: 1.5px solid #6ee7b7;
border-radius: 10px;
box-shadow: 0 1px 4px rgb(16 185 129 / 12%);
&--loading {
background: #f8fafc;
border-color: #cbd5e1;
align-items: center;
gap: 10px;
color: #475569;
font-size: 13px;
}
&__spinner {
width: 18px;
height: 18px;
border: 2px solid #e2e8f0;
border-top-color: #3b82f6;
border-radius: 50%;
animation: ws-spin 0.7s linear infinite;
flex-shrink: 0;
}
&__icon {
color: #10b981;
font-size: 22px;
width: 22px;
height: 22px;
flex-shrink: 0;
margin-top: 2px;
}
&__content {
flex: 1;
min-width: 0;
}
&__title {
font-size: 13px;
font-weight: 700;
color: #065f46;
margin-bottom: 4px;
}
&__details {
display: flex;
flex-wrap: wrap;
gap: 4px 6px;
font-size: 12px;
color: #374151;
}
&__note {
margin-top: 6px;
font-size: 11px;
color: #6b7280;
}
&__clear {
background: none;
border: none;
cursor: pointer;
color: #9ca3af;
padding: 2px;
line-height: 1;
display: flex;
align-items: center;
border-radius: 4px;
flex-shrink: 0;
&:hover {
color: #6b7280;
background: #f1f5f9;
}
mat-icon {
font-size: 18px;
width: 18px;
height: 18px;
}
}
}
.ws-wb-sep {
color: #9ca3af;
}
@keyframes ws-spin {
to {
transform: rotate(360deg);
}
}