feat(clerk): block clerk cash deposits with UI banner and save guard

Clerk cash deposits are not permitted — notes/coins denomination is
cashier vault responsibility. Shows warning banner above footer and
disables Save button when clerk selects cash payment type on a deposit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-06-15 14:58:44 +05:30
co-authored by Claude Sonnet 4.6
parent 8e64f82c0a
commit fb1f79c889
3 changed files with 57 additions and 14 deletions
@@ -1355,6 +1355,19 @@
</div>
</div>
} @else {
@if (isClerkCashDeposit) {
<div class="te-day-block te-day-block--warning">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
<line x1="12" y1="9" x2="12" y2="13" />
<line x1="12" y1="17" x2="12.01" y2="17" />
</svg>
<span>
<strong>CASH DEPOSIT NOT PERMITTED</strong> — Cash deposits can only be processed by the cashier.
Notes/coins denomination is managed at the cashier vault.
</span>
</div>
}
<div class="ws-footer">
<div class="ws-footer__stats">
<button class="ws-btn ws-btn--ghost ws-btn--exit" type="button" (click)="onExit()">Exit</button>
@@ -1364,7 +1377,12 @@
<button class="ws-btn ws-btn--mode" type="button">Change Tr.Mode</button>
</div>
<div class="ws-footer__actions">
<button class="ws-btn ws-btn--post" type="button" (click)="onSave()" [disabled]="saving">
<button
class="ws-btn ws-btn--post"
type="button"
(click)="onSave()"
[disabled]="saving || isClerkCashDeposit"
>
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
<polyline points="20 6 9 17 4 12" />
</svg>
@@ -1747,6 +1747,11 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
return !!pt?.isCashPayment;
}
/** Clerk attempting a cash deposit — blocked because cash/denomination is cashier's responsibility */
get isClerkCashDeposit(): boolean {
return this.isClerk && this.isCashPayment && this.selectedRow?.dbCr === 'Cr';
}
/** For cash: amount comes ONLY from denomination total */
get effectiveAmount(): number {
if (this.isCashPayment) return this.denominationTotal;
@@ -2028,6 +2033,15 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
const isDeposit = row.dbCr === 'Cr';
// Clerk cash deposit is not permitted — cash handling (notes/coins) is cashier's responsibility
if (this.isClerk && isDeposit && isCash) {
this.showAlert('error', 'Cash Deposit Not Permitted', [
'Cash deposits cannot be processed by a clerk.',
'Only the cashier can accept cash deposits (notes/coins denomination is managed at cashier vault).'
]);
return;
}
// Token number is required for all withdrawals (clerk must enter, cashier must enter approved token)
if (!this.reviewMode && !isDeposit && !row.tokNo?.trim()) {
this.showAlert('error', 'Token Number Required', [
@@ -2281,9 +2295,11 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
private openConfirmDialog(row: TxnRow, amount: number, isDeposit: boolean, selectedPt: any): void {
// Token-based cashier disbursement: balance was already deducted at manager approval
const isDisbursement = !isDeposit && !this.isClerk && !!this.activeWithdrawalRequest;
// Cashier deposit (savings/current) goes via maker-checker: balance credits only after manager approval
// Cashier deposit (savings/current/GL) goes via maker-checker: balance credits only after manager approval
const isDepositRequest =
isDeposit && !this.isClerk && (row.acTyp === 'SD' || row.acTyp === 'CA' || row.acTyp === 'CB');
isDeposit &&
!this.isClerk &&
(row.acTyp === 'SD' || row.acTyp === 'CA' || row.acTyp === 'CB' || row.acTyp === 'GL');
const balanceAfterTxn =
isDisbursement || isDepositRequest
? this.currentBalance // no immediate balance change
@@ -2323,8 +2339,9 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
const accountId = this.accountDetails.id;
const cfg = this.accountTypeConfig(row.acTyp);
// GL type → post as manual journal entry
if (row.acTyp === 'GL') {
// GL withdrawal → direct journal entry (no maker-checker)
// GL deposit → falls through to maker-checker block below
if (row.acTyp === 'GL' && !isDeposit) {
this.submitGlJournalEntry(row, amount, isDeposit);
return;
}
@@ -2335,13 +2352,13 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
return;
}
// Cashier deposit (savings/current) → route through maker-checker (create PENDING request, no direct credit)
// Cashier deposit (savings/current/GL) → route through maker-checker (create PENDING request, no direct credit)
// Use the dialog-confirmed amount — for cash that is the denomination total
if (
!this.isClerk &&
!this.reviewMode &&
isDeposit &&
(row.acTyp === 'SD' || row.acTyp === 'CA' || row.acTyp === 'CB')
(row.acTyp === 'SD' || row.acTyp === 'CA' || row.acTyp === 'CB' || row.acTyp === 'GL')
) {
this.submitCashierDepositRequest(row, this.confirmData?.amount ?? amount);
return;
@@ -2700,12 +2717,20 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
// deposit would fail the backend denomination-total check
const isCash = !!selectedPt?.isCashPayment;
const isGl = row.acTyp === 'GL';
return {
accountNo: this.accountDetails.accountNo,
accountType: row.acTyp === 'CA' || row.acTyp === 'CB' ? 'CURRENT' : 'SAVINGS',
savingsAccountId: this.accountDetails.id,
clientId: this.accountDetails.clientId ?? this.clientDetails?.id ?? null,
clientName: this.clientDetails?.displayName ?? this.accountDetails?.clientName ?? null,
accountNo: isGl ? (this.accountDetails.glCode ?? this.accountDetails.name) : this.accountDetails.accountNo,
accountType: isGl ? 'GL' : row.acTyp === 'CA' || row.acTyp === 'CB' ? 'CURRENT' : 'SAVINGS',
savingsAccountId: isGl ? null : this.accountDetails.id,
// GL-specific fields — ignored by backend for non-GL types
glAccountId: isGl ? this.accountDetails.id : null,
contraGlId: isGl ? this.cashVaultGlId : null,
officeId: isGl ? row.br || 1 : null,
currencyCode: isGl ? (this.accountDetails?.currency?.code ?? 'INR') : null,
clientId: isGl ? null : (this.accountDetails.clientId ?? this.clientDetails?.id ?? null),
clientName: isGl
? (this.accountDetails?.name ?? null)
: (this.clientDetails?.displayName ?? this.accountDetails?.clientName ?? null),
amount,
txnDate: txnDateIso,
paymentTypeId: row.paymentTypeId || null,
+2 -2
View File
@@ -9,8 +9,8 @@
/* tslint:disable */
export default {
'mifos_x': {
'version': '260609',
'hash': 'f7d9259e'
'version': '260615',
'hash': 'e5d6cd32'
},
'allow_switching_backend_instance': true
};