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:
co-authored by
Claude Sonnet 4.6
parent
de420bb8ab
commit
61cb09f352
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user