diff --git a/src/app/cashier/transaction-entry/transaction-entry.component.html b/src/app/cashier/transaction-entry/transaction-entry.component.html index ffc38955f..c429ad732 100644 --- a/src/app/cashier/transaction-entry/transaction-entry.component.html +++ b/src/app/cashier/transaction-entry/transaction-entry.component.html @@ -184,7 +184,7 @@ class="ws-f__ctl" [(ngModel)]="row.acTyp" (click)="$event.stopPropagation()" - (change)="isGlCreditOnly(row) && (row.dbCr = 'Cr')" + (change)="isCreditOnly(row) && (row.dbCr = 'Cr')" > @for (a of acTypOptionsFor(row); track a) { @@ -267,7 +267,7 @@ type="button" class="ws-seg__btn ws-seg__btn--dr" [class.ws-seg__btn--active-dr]="row.dbCr === 'Dr'" - [disabled]="isGlCreditOnly(row)" + [disabled]="isCreditOnly(row)" (click)="row.dbCr = 'Dr'" > DR @@ -1644,7 +1644,7 @@ class="ws-f__ctl" [(ngModel)]="row.acTyp" (click)="$event.stopPropagation()" - (change)="isGlCreditOnly(row) && (row.dbCr = 'Cr')" + (change)="isCreditOnly(row) && (row.dbCr = 'Cr')" > @for (a of acTypOptionsFor(row); track a) { @@ -1715,7 +1715,7 @@ type="button" class="ws-seg__btn ws-seg__btn--dr" [class.ws-seg__btn--active-dr]="row.dbCr === 'Dr'" - [disabled]="isGlCreditOnly(row)" + [disabled]="isCreditOnly(row)" (click)="$event.stopPropagation(); row.dbCr = 'Dr'" > DR diff --git a/src/app/cashier/transaction-entry/transaction-entry.component.ts b/src/app/cashier/transaction-entry/transaction-entry.component.ts index 413652c93..f0be400ff 100644 --- a/src/app/cashier/transaction-entry/transaction-entry.component.ts +++ b/src/app/cashier/transaction-entry/transaction-entry.component.ts @@ -2344,6 +2344,20 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { ); } + /** + * A Fixed Deposit row is credit-only on both the clerk and the cashier screens: an FD is funded once, with + * exactly its principal, and money leaves it only through its maturity or premature closure. The one + * exception is the clerk's own "Close this account" flow, which raises a closure request. + */ + isFdCreditOnly(row: TxnRow): boolean { + return row.acTyp === 'FD' && !this.pendingClosure; + } + + /** DR is not offered for this row (see isGlCreditOnly and isFdCreditOnly). */ + isCreditOnly(row: TxnRow): boolean { + return this.isGlCreditOnly(row) || this.isFdCreditOnly(row); + } + /** Called when the TYPE dropdown changes — sets sensible defaults for TXN TP and DR/CR */ /** * A cashier row that was filled from an approved token (withdrawal / account-closure) is cash @@ -5272,6 +5286,20 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { } onSave(): void { + // Money never leaves a Fixed Deposit from this screen, clerk or cashier: no withdrawal slip, payment, + // collection or transfer debits an FD. It is paid out only through its maturity or premature closure + // (the clerk's "Close this account" flow). The DR button is already disabled for FD rows (isFdCreditOnly); + // this also catches a row set to DR by its TYPE, such as a Withdrawal Slip. + const fdDebitRows = this.rows.filter((r) => this.isFdCreditOnly(r) && r.dbCr === 'Dr'); + if (fdDebitRows.length > 0) { + this.showAlert('error', 'Fixed Deposit Cannot Be Debited', [ + `FD account(s) ${fdDebitRows.map((r) => r.acNo).join(', ')} cannot be debited or withdrawn from.`, + 'Money leaves a Fixed Deposit only through its maturity or premature closure.', + 'An FD can only be credited — once, with exactly its principal.' + ]); + return; + } + // Demand drafts commit here like everything else on the worksheet. Checked first because a DD // row shares its shape with the ordinary entries below — without this it would fall through and // post as a plain transaction, with the draft never raised and the charges silently dropped. @@ -5349,7 +5377,9 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { return; } - // Bank transfer: only savings (SD) or current (CA/CB) accounts allowed + // Bank transfer: savings (SD) or current (CA/CB) accounts on either side, plus a Fixed Deposit (FD) on + // the CR side only -- funding it from the customer's own account. The server checks the funding rules + // (first and only funding, exactly the principal, the source account's owner and available balance). const allowedTypes = new Set([ 'SD', 'CA', @@ -5357,13 +5387,13 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { ]); const invalidAccounts = [ ...drRows, - ...crRows + ...crRows.filter((r) => r.acTyp !== 'FD') ].filter((r) => !allowedTypes.has(r.acTyp)); if (invalidAccounts.length > 0) { this.showAlert('error', 'Invalid Account Type', [ `Account type(s) not allowed in Bank Transfer: ${invalidAccounts.map((r) => r.acTyp + ' (' + r.acNo + ')').join(', ')}`, - 'Only Savings (SD) and Current (CA) accounts are permitted.', - 'GL, FD and SHR accounts are not allowed — use Disbursement/Collection instead.', + 'Debit side: only Savings (SD) and Current (CA) accounts. Credit side: SD, CA or a Fixed Deposit (FD).', + 'GL and SHR accounts are not allowed — use Disbursement/Collection instead.', 'RD installments are paid through a cashier Receipt or Auto Debit.' ]); return;