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;