feat(cashier): integrate expected balance, settlement verification, and reconciliation checks
This commit is contained in:
@@ -175,6 +175,35 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (totalVariance !== 0) {
|
||||
<div
|
||||
class="settle-variance-banner"
|
||||
[class.settle-variance-banner--excess]="totalVariance > 0"
|
||||
[class.settle-variance-banner--short]="totalVariance < 0"
|
||||
style="margin: 8px 0 0"
|
||||
>
|
||||
<mat-icon>{{ totalVariance > 0 ? 'add_circle_outline' : 'remove_circle_outline' }}</mat-icon>
|
||||
<div class="settle-variance-text">
|
||||
<strong
|
||||
>{{ totalVariance > 0 ? 'Excess' : 'Shortage' }}: ₹{{
|
||||
(totalVariance < 0 ? -totalVariance : totalVariance) | number: '1.2-2'
|
||||
}}</strong
|
||||
>
|
||||
<span>Variance reason is required to save reconciliation.</span>
|
||||
</div>
|
||||
<mat-form-field appearance="outline" subscriptSizing="dynamic" style="flex-shrink: 0; width: 300px">
|
||||
<mat-label>Variance Reason *</mat-label>
|
||||
<input
|
||||
matInput
|
||||
type="text"
|
||||
[value]="varianceReason"
|
||||
(input)="varianceReason = $any($event.target).value"
|
||||
placeholder="e.g. Rounding, soiled note returned"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="pg-total-bar">
|
||||
<span>
|
||||
Expected: <strong>₹{{ totalExpected | number: '1.2-2' }}</strong> | Physical:
|
||||
@@ -184,9 +213,14 @@
|
||||
</strong>
|
||||
<span class="badge" [ngClass]="statusClass(statusLabel)">{{ statusLabel }}</span>
|
||||
</span>
|
||||
<button type="button" class="pg-btn pg-btn--primary" [disabled]="!valid || saving" (click)="submit()">
|
||||
<mat-icon>save</mat-icon> Save Reconciliation
|
||||
</button>
|
||||
<div style="display: flex; align-items: center; gap: 10px">
|
||||
@if (totalVariance !== 0 && !varianceReason.trim()) {
|
||||
<span style="font-size: 12px; color: #d97706; font-weight: 600">Enter variance reason to save</span>
|
||||
}
|
||||
<button type="button" class="pg-btn pg-btn--primary" [disabled]="!valid || saving" (click)="submit()">
|
||||
<mat-icon>save</mat-icon> Save Reconciliation
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -45,3 +45,69 @@
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
/* Variance banner (shared style with settlement) */
|
||||
.settle-variance-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid;
|
||||
|
||||
mat-icon {
|
||||
font-size: 22px;
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&--excess {
|
||||
background: #f0fdf4;
|
||||
border-color: #86efac;
|
||||
color: #15803d;
|
||||
|
||||
mat-icon {
|
||||
color: #16a34a;
|
||||
}
|
||||
}
|
||||
|
||||
&--short {
|
||||
background: #fef2f2;
|
||||
border-color: #fca5a5;
|
||||
color: #991b1b;
|
||||
|
||||
mat-icon {
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settle-variance-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 12px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.pos-section-label {
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #1e40af;
|
||||
padding: 10px 12px 4px;
|
||||
|
||||
&--coin {
|
||||
color: #92400e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ export class ReconciliationComponent implements OnInit {
|
||||
cashierName = '';
|
||||
reconDate = '';
|
||||
note = '';
|
||||
varianceReason = '';
|
||||
rows: DenomRow[] = [];
|
||||
loadingExpected = false;
|
||||
saving = false;
|
||||
@@ -162,7 +163,11 @@ export class ReconciliationComponent implements OnInit {
|
||||
return v === 0 ? 'OK' : v > 0 ? 'EXCESS' : 'SHORT';
|
||||
}
|
||||
get valid(): boolean {
|
||||
return !!this.selectedCashierId && this.rows.length > 0;
|
||||
return (
|
||||
!!this.selectedCashierId &&
|
||||
this.rows.length > 0 &&
|
||||
(this.totalVariance === 0 || this.varianceReason.trim().length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
@@ -173,7 +178,13 @@ export class ReconciliationComponent implements OnInit {
|
||||
tellerId: this.selectedTellerId,
|
||||
cashierName: this.cashierName,
|
||||
reconDate: this.reconDate,
|
||||
note: this.note || null,
|
||||
note:
|
||||
[
|
||||
this.note,
|
||||
this.totalVariance !== 0 ? `Variance reason: ${this.varianceReason}` : null
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' | ') || null,
|
||||
denominations: this.rows.map((r) => ({
|
||||
denominationId: r.denominationId,
|
||||
denominationValue: r.denominationValue,
|
||||
@@ -190,6 +201,7 @@ export class ReconciliationComponent implements OnInit {
|
||||
});
|
||||
this.rows = [];
|
||||
this.note = '';
|
||||
this.varianceReason = '';
|
||||
this.saving = false;
|
||||
this.loadHistory();
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user