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:
co-authored by
Claude Sonnet 4.6
parent
8e64f82c0a
commit
fb1f79c889
@@ -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,
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
/* tslint:disable */
|
||||
export default {
|
||||
'mifos_x': {
|
||||
'version': '260609',
|
||||
'hash': 'f7d9259e'
|
||||
'version': '260615',
|
||||
'hash': 'e5d6cd32'
|
||||
},
|
||||
'allow_switching_backend_instance': true
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user