feat: cash-only registers, day book sections and DD amendments
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s

This commit is contained in:
2026-09-03 18:26:22 +05:30
parent 6442562df0
commit fbce73fd17
111 changed files with 9076 additions and 3507 deletions
@@ -32,6 +32,7 @@ import {
} from '../../shared/customer-search-dialog/customer-search-dialog.component';
import { SponsorDDService } from '../../sponsor-dd/sponsor-dd.service';
import { OrganizationService } from '../../organization/organization.service';
import { CashierIssueDdDialogComponent } from './cashier-issue-dd-dialog.component';
interface TxnRow {
typ: string;
@@ -51,6 +52,7 @@ interface TxnRow {
routingCode: string;
/** DD issuance charges/commission — only used when TYPE=DD, TXN TP=Cash, AC.TP=GL */
ddCharges: number | null;
sponsorBankId?: number | null;
/** Only used when this row is AC.TP=SHR inside an Enter Bank Transfer to its linked savings
* account — governs the share redemption (see submitShareRedemptionTransfer()). */
resolutionNumber?: string;
@@ -1523,7 +1525,14 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
* the one that actually governs how the settlement credit posts (see submitShareRedemptionTransfer()).
*/
paymentTypesForRow(row: TxnRow): any[] {
const tellerFacing = this.paymentTypes.filter((pt: any) => !pt.isSystemDefined);
// Sponsor-bank payment types are excluded. They were added for the Demand Draft module, which
// picks its bank from m_sponsor_bank on the DD row itself, and they are not counter instruments
// — a clerk choosing "SBI" as the TXN TP. of a withdrawal says nothing about how the money
// moves. They are recognised by their description rather than by name so that adding another
// sponsor bank does not put it back on this list.
const tellerFacing = this.paymentTypes.filter(
(pt: any) => !pt.isSystemDefined && !/sponsor bank/i.test(pt.description ?? '')
);
if (!this.isClerk) {
// Cashier counter takes cash only — cheque/transfer/DD instruments go through their own
// clerk/back-office approval flows, never straight through the cash drawer.
@@ -1541,6 +1550,23 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
if (isShareSettlementCredit) {
return tellerFacing.filter((pt: any) => !pt.isCashPayment);
}
// A withdrawal slip is a cash instruction and nothing else: the clerk raises it, the cashier
// counts the notes out against the token. There is no non-cash way to settle one, so cash is
// the only choice offered rather than the default among several.
if (row.typ === 'Withdrawal Slip') {
return tellerFacing.filter((pt: any) => !!pt.isCashPayment);
}
// A clerk's Demand Draft is never funded by cash they take themselves. Cash for a draft is
// counted at the counter, so it comes either from the cashier's own DD screen or from an
// approved cashier deposit request the clerk points the draft at; the clerk's own route is to
// debit the member's account. Offering cash here let a draft be raised against notes no drawer
// had counted, which is exactly how the six cash DDs with no denomination breakdown arose.
if (row.typ === 'DD') {
return tellerFacing.filter((pt: any) => !pt.isCashPayment);
}
// Everything else can go either way, so both are offered and the clerk says which. A cheque or
// a bank transfer normally settles through the instrument, a disbursement or fund transfer
// between accounts — the type alone does not fix it to one or the other.
return tellerFacing;
}
@@ -1664,9 +1690,6 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
// wrong for crediting a GL amount into a savings/current account for any other reason (e.g.
// funding a newly-activated account's opening balance from a GL source).
{ value: 'Fund Transfer', label: 'Fund Transfer' },
// NOTE: the exact same TxnRow.typ string is used at every dispatch/default-setting site below —
// search for 'Disbursement' whenever this needs to change again, since 'Fund Transfer' always
// rides alongside it in every one of those checks rather than having its own separate case.
{ value: 'Enter Bank Transfer', label: 'Enter Bank Transfer' },
{ value: 'DD', label: 'DD (Demand Draft)' }
];
@@ -1692,6 +1715,9 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
return this.isClerk ? this.clerkTypOptions : this.cashierTypOptions;
}
sponsorBanks: any[] = [];
ddGlMapping: any = null;
// ── Denomination ──────────────────────────────────────────────────────────
notes: Denomination[] = [];
coins: Denomination[] = [];
@@ -1752,6 +1778,7 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
this.loadCashVaultGl();
this.loadDdGstPercentage();
this.loadSponsorBanks();
if (this.reviewMode) {
this.loadRequestIntoForm(this.reviewRequestId!, 'review');
@@ -1842,12 +1869,85 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
.getGlMappings()
.pipe(catchError(() => of([])))
.subscribe((mappings: any[]) => {
const mapping = mappings && mappings.length ? mappings[0] : null;
this.ddGstPercentage = Number(mapping?.gstPercentage ?? 18);
this.ddLimitAmount = mapping?.ddLimitAmount != null ? Number(mapping.ddLimitAmount) : null;
this.ddGlMapping = mappings && mappings.length ? mappings[0] : null;
this.ddGstPercentage = Number(this.ddGlMapping?.gstPercentage ?? 18);
this.ddLimitAmount = this.ddGlMapping?.ddLimitAmount != null ? Number(this.ddGlMapping.ddLimitAmount) : null;
if (this.ddGlMapping?.ddPayableGlAccountId) {
if (this.ddGlMapping.ddPayableGlCode) {
this.applyDdGlMappingToDdRows();
} else {
this.http
.get<any>(`/glaccounts/${this.ddGlMapping.ddPayableGlAccountId}`)
.pipe(catchError(() => of(null)))
.subscribe((glAcc: any) => {
if (glAcc) {
this.ddGlMapping.ddPayableGlCode = glAcc.glCode || String(glAcc.id);
this.ddGlMapping.ddPayableGlAccountName = glAcc.name;
}
this.applyDdGlMappingToDdRows();
});
}
}
});
}
applyDdGlMappingToRow(row: TxnRow): void {
if (row.typ !== 'DD' || !this.ddGlMapping) return;
// Leaves an account-funded DD alone: its A/C NO. is the member's account the clerk typed, and
// overwriting it with the DD Payable GL would silently turn the row back into a cash draft.
if (this.isDdAccountDebitRow(row)) return;
row.acTyp = 'GL';
row.dbCr = 'Cr';
if (this.ddGlMapping.ddPayableGlCode) {
row.acNo = this.ddGlMapping.ddPayableGlCode;
this.selectedRow = row;
this.loadAccountDetails(row.acNo, 'GL');
return;
}
if (this.ddGlMapping.ddPayableGlAccountId) {
this.http
.get<any>(`/glaccounts/${this.ddGlMapping.ddPayableGlAccountId}`)
.pipe(catchError(() => of(null)))
.subscribe((glAcc: any) => {
if (glAcc) {
this.ddGlMapping.ddPayableGlCode = glAcc.glCode || String(glAcc.id);
this.ddGlMapping.ddPayableGlAccountName = glAcc.name;
row.acNo = this.ddGlMapping.ddPayableGlCode;
this.selectedRow = row;
this.loadAccountDetails(row.acNo, 'GL');
}
});
}
}
private applyDdGlMappingToDdRows(): void {
for (const row of this.rows) {
if (row.typ === 'DD') {
this.applyDdGlMappingToRow(row);
}
}
}
private loadSponsorBanks(): void {
this.sponsorDDService
.getSponsorBanks()
.pipe(catchError(() => of([])))
.subscribe((banks: any[]) => {
this.sponsorBanks = banks || [];
for (const row of this.rows) {
if (row.typ === 'DD' && !row.sponsorBankId && this.sponsorBanks.length > 0) {
row.sponsorBankId = this.sponsorBanks[0].id;
}
}
});
}
get isDdGlMappingConfigured(): boolean {
return !!(this.ddGlMapping && this.ddGlMapping.ddPayableGlAccountId && this.ddGlMapping.cashGlAccountId);
}
private loadDayStatus(): void {
this.cashierService
.getCashierDayStatus()
@@ -2093,9 +2193,12 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
row.chequeNo = '';
row.cheqDate = '';
break;
// A cheque clears through the bank rather than the drawer, so it opens on the transfer
// instrument. Cash is still on its dropdown — a cheque can be cashed over the counter — but
// it is the exception, not what the field should start on.
case 'Cheque':
row.dbCr = 'Dr';
row.paymentTypeId = this.findCashPaymentTypeId();
row.paymentTypeId = this.findTransferPaymentTypeId();
break;
case 'Disbursement':
case 'Fund Transfer':
@@ -2121,16 +2224,25 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
row.chequeNo = '';
row.cheqDate = '';
break;
// A DD is bought with cash: the customer hands over Face Value + Charges + GST, so it is money
// coming IN (Cr) against the GL the draft is drawn on. All three of Cr, a cash payment type and
// AC.TP = GL are what isDdCashGlRow() looks for -- without them the DD Charges & GST panel never
// appears and the row would post as an ordinary entry with the charges silently dropped.
// A DD is bought with cash: the purchaser hands over Face Value + Charges + GST, so it is
// money coming IN (Cr) against the GL the draft is drawn on. All three of Cr, a cash payment
// type and AC.TP = GL are what isDdCashGlRow() looks for -- without them the DD Charges & GST
// panel never appears and the row would post as an ordinary entry with the charges silently
// dropped.
//
// A clerk funds a draft through an account or an instrument instead, and their TXN TP. list no
// longer offers cash, so defaulting to it would open the field on a value it cannot show.
// isDdCashGlRow() still holds for them through AC.TP = GL.
case 'DD':
row.dbCr = 'Cr';
row.paymentTypeId = this.findCashPaymentTypeId();
row.acTyp = 'GL';
row.paymentTypeId = this.isClerk ? this.findTransferPaymentTypeId() : this.findCashPaymentTypeId();
row.chequeNo = '';
row.cheqDate = '';
if (this.sponsorBanks.length > 0 && !row.sponsorBankId) {
row.sponsorBankId = this.sponsorBanks[0].id;
}
this.applyDdGlMappingToRow(row);
break;
}
}
@@ -2145,10 +2257,33 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
return pt?.id ?? null;
}
/** TYPE=DD + TXN TP=Cash + AC.TP=GL — the only combination that collects DD charges + GST from the customer. */
/** TYPE=DD + TXN TP=Cash */
isDdCashGlRow(row: TxnRow): boolean {
if (this.isDdAccountDebitRow(row)) return false;
const pt = this.paymentTypes.find((p: any) => p.id === row.paymentTypeId);
return row.typ === 'DD' && !!pt?.isCashPayment && row.acTyp === 'GL';
return row.typ === 'DD' && (row.acTyp === 'GL' || !row.acTyp || !!pt?.isCashPayment);
}
/**
* TYPE=DD funded by debiting a member's savings/current account rather than by cash.
*
* Clerk-only. A cashier's counter takes cash and nothing else, so offering them an account debit
* would let a draft be raised against an account with no cash to reconcile it against — and the
* clerk flow is the one that carries a maker-checker step over the member's balance.
*/
isDdAccountDebitRow(row: TxnRow): boolean {
return this.isClerk && row.typ === 'DD' && (row.acTyp === 'SD' || row.acTyp === 'CA' || row.acTyp === 'CB');
}
/**
* Any DD row the charges/GST panel applies to.
*
* Charges and GST come from the admin's DD configuration and are collected whichever way the
* draft is paid for — the difference is only where the money comes from, so the panel is the same
* for both and the label on the total changes rather than the arithmetic.
*/
isDdRow(row: TxnRow): boolean {
return this.isDdCashGlRow(row) || this.isDdAccountDebitRow(row);
}
/** GST on DD charges only (never on the DD face value), at the admin-configured rate. */
@@ -2179,6 +2314,258 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
return !!this.selectedRow && Math.abs(this.denominationTotal - this.ddTargetTotal) < 0.005;
}
branchNameFor(branchId: number): string {
const map: Record<number, string> = {
1: '001 · Main',
2: '002 · M.G. Road',
14: '014 · Station Rd',
27: '027 · Industrial'
};
return map[branchId] || `Branch ${branchId}`;
}
/**
* Cashier Walk-in DD creation flow:
* 1. Validates one-time persistent GL configuration
* 2. Auto-selects next available leaflet
* 3. Opens dialog to capture purchaser/beneficiary details
* 4. Submits issue request directly with physical denominations for manager approval
*/
openIssueWalkinDd(row: TxnRow): void {
if (!this.isDdGlMappingConfigured) {
this.showAlert('error', 'DD GL Mapping Not Configured', [
'DD GL mapping is not configured.',
'Please ask an administrator to set up the GL accounts in Admin > Sponsor DD GST / GL Settings before creating Demand Drafts.'
]);
return;
}
const faceValue = Number(row.amount);
if (!faceValue || faceValue <= 0) {
this.showAlert('error', 'Invalid Amount', ['Please enter the DD Face Value amount before proceeding.']);
return;
}
const chargesAmount = Number(row.ddCharges) || 0;
const gstAmount = this.ddGstAmountFor(row);
const totalCashPayable = this.ddTotalPayableFor(row);
const accountDebit = this.isDdAccountDebitRow(row);
// An account-funded draft needs the account resolved to a Fineract id before it can be sent:
// the server takes a savings/current account id and a client id, not the typed number. Both are
// filled in by the A/C NO. lookup, so an unresolved account means the number was never found.
let fundingAccountId: number | null = null;
let fundingClientId: number | null = null;
if (accountDebit) {
const acNo = (row.acNo || '').trim();
if (!acNo) {
this.showAlert('error', 'Account Required', [
'Enter the account number the Demand Draft is funded from.'
]);
return;
}
fundingAccountId = this.rowAccountIds[acNo] ?? null;
fundingClientId = this.rowClientIds[acNo] ?? null;
if (!fundingAccountId || !fundingClientId) {
this.showAlert('error', 'Account Not Found', [
`Account ${acNo} could not be resolved.`,
'Check the account number and wait for its details to load before raising the draft.'
]);
return;
}
}
// Only a cash draft has notes to reconcile. An account debit takes no cash at the counter, so a
// denomination count left over from an earlier row must not be checked against it.
if (!accountDebit && this.denominationTotal > 0 && Math.abs(this.denominationTotal - totalCashPayable) > 0.005) {
this.showAlert('error', 'Denomination Mismatch', [
`Cash denomination counted (₹${this.denominationTotal}) does not match total payable cash (₹${totalCashPayable}).`,
'Please verify and match the physical cash denomination count.'
]);
return;
}
this.saving = true;
this.sponsorDDService.getNextAvailableLeaflet(row.br, row.sponsorBankId ?? undefined).subscribe({
next: (leaflet) => {
this.saving = false;
if (!leaflet) {
this.showAlert('error', 'No Available Leaflets', [
'No available Demand Draft leaflets found for this branch and sponsor bank.',
'Please register a DD inventory series first.'
]);
return;
}
const selectedBank = this.sponsorBanks.find((b: any) => b.id === row.sponsorBankId);
const branchName = this.branchNameFor(row.br) || 'Branch ' + row.br;
const dialogRef = this.dialog.open(CashierIssueDdDialogComponent, {
width: '600px',
disableClose: true,
data: {
leaflet,
branchName,
sponsorBankName: selectedBank?.name || 'Sponsor Bank',
faceValue,
chargesAmount,
gstPercentage: this.ddGstPercentage,
gstAmount,
totalCashPayable,
paymentMethod: accountDebit ? ('ACCOUNT_DEBIT' as const) : ('CASH' as const),
accountNo: accountDebit ? row.acNo : undefined,
accountHolderName: accountDebit ? this.rowAccountNames[(row.acNo || '').trim()] : undefined
}
});
dialogRef.afterClosed().subscribe((result) => {
if (!result) return;
if (accountDebit) {
this.submitAccountDebitDd(row, leaflet, result, fundingAccountId!, fundingClientId!);
} else {
this.submitWalkinCashDd(row, leaflet, result);
}
});
},
error: (err) => {
this.saving = false;
this.showAlert('error', 'Error Auto-Selecting DD Leaflet', [err.error?.message || err.message]);
}
});
}
/**
* Raises a Demand Draft funded by debiting a member's savings or current account.
*
* Clerk-only, and a maker-checker request like the cash flow: nothing is debited here. The member's
* account is only touched when a checker approves, which is also when the balance is re-checked —
* so a draft raised against a sufficient balance can still be refused if the balance has since
* gone. No denominations are sent, because no cash is taken.
*
* The account goes in savingsAccountId or currentAccountId, never both: the server insists on
* exactly one, since which table the account lives in decides which control GL is debited.
*/
private submitAccountDebitDd(
row: TxnRow,
leaflet: any,
beneficiaryData: any,
fundingAccountId: number,
fundingClientId: number
): void {
const d = new Date();
const issuedDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
const isCurrent = row.acTyp === 'CA' || row.acTyp === 'CB';
const payload = {
clientId: fundingClientId,
beneficiaryName: beneficiaryData.beneficiaryName,
payableAtBranch: beneficiaryData.payableAtBranch,
payableAtCity: beneficiaryData.payableAtCity,
faceValue: Number(row.amount),
chargesAmount: Number(row.ddCharges) || 0,
issuingBranchId: row.br,
issuedDate,
paymentMethod: 'ACCOUNT_DEBIT' as const,
savingsAccountId: isCurrent ? undefined : fundingAccountId,
currentAccountId: isCurrent ? fundingAccountId : undefined
};
this.saving = true;
this.sponsorDDService.submitIssueRequest(leaflet.id, payload).subscribe({
next: () => {
this.saving = false;
this.showAlert('success', 'DD Issuance Request Submitted', [
`Demand Draft request for DD No. ${leaflet.ddNumber} (Face Value: ₹${row.amount}) submitted successfully!`,
`₹${this.ddTotalPayableFor(row)} will be debited from ${row.acNo} on approval.`,
'Sent to Manager / Head Cashier for maker-checker approval.'
]);
row.amount = null;
row.ddCharges = null;
row.description = '';
},
error: (err) => {
this.saving = false;
this.showAlert('error', 'DD Submission Failed', [err.error?.message || err.message]);
}
});
}
private submitWalkinCashDd(row: TxnRow, leaflet: any, purchaserData: any): void {
// Read through countFor, which knows the map is keyed by kind:value. Indexing by d.id looked
// right but matched nothing, so every cash DD went in with no breakdown at all and its money
// vanished from the closing report -- the notes were counted on screen and then dropped.
const denominationMap: { [key: string]: number } = {};
for (const d of [
...this.notes,
...this.coins
]) {
const count = this.countFor(d);
if (count > 0) {
denominationMap[this.denomKey(d)] = count;
}
}
// The breakdown is the only record of which notes were taken, and the server insists it add up
// to the cash collected. Checked here so the cashier is told which figure is wrong while the
// counts are still on screen, rather than getting a rejection after the fact.
//
// ddTotalPayableFor rounds up to the whole rupee actually collected, which is what the notes
// can add to; the exact figure with paise is accepted too, for a draft that lands on a rupee.
const totalCollected = Number(this.ddTotalPayableFor(row));
const exactPayable = Number(row.amount || 0) + (Number(row.ddCharges) || 0) + this.ddGstAmountFor(row);
const countedTotal = Object.entries(denominationMap).reduce(
(sum, [
key,
count
]) => sum + Number(key.split(':')[1]) * count,
0
);
if (countedTotal !== totalCollected && countedTotal !== exactPayable) {
this.showAlert('error', 'Denomination Entry Does Not Match', [
countedTotal === 0
? 'Enter the notes and coins taken from the purchaser before submitting.'
: `The notes and coins entered come to ₹${countedTotal.toFixed(2)}, but ₹${totalCollected.toFixed(2)} is collected (face value + charges + GST).`,
'The breakdown must equal the cash taken — it is what the closing report and the drawer are checked against.'
]);
return;
}
const d = new Date();
const issuedDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
const payload = {
walkinPurchaserName: purchaserData.walkinPurchaserName,
walkinPurchaserAddress: purchaserData.walkinPurchaserAddress,
walkinPurchaserAadhaar: purchaserData.walkinPurchaserAadhaar,
beneficiaryName: purchaserData.beneficiaryName,
payableAtBranch: purchaserData.payableAtBranch,
payableAtCity: purchaserData.payableAtCity,
faceValue: Number(row.amount),
chargesAmount: Number(row.ddCharges) || 0,
issuingBranchId: row.br,
issuedDate,
paymentMethod: 'CASH' as const,
denominationData: Object.keys(denominationMap).length > 0 ? JSON.stringify(denominationMap) : undefined
};
this.saving = true;
this.sponsorDDService.submitIssueRequest(leaflet.id, payload).subscribe({
next: () => {
this.saving = false;
this.showAlert('success', 'DD Issuance Request Submitted', [
`Demand Draft request for DD No. ${leaflet.ddNumber} (Face Value: ₹${row.amount}, Total Cash: ₹${this.ddTotalPayableFor(row)}) submitted successfully!`,
'Sent to Manager / Head Cashier for maker-checker approval.'
]);
row.amount = null;
row.ddCharges = null;
row.description = '';
this.clearDenominations();
},
error: (err) => {
this.saving = false;
this.showAlert('error', 'DD Submission Failed', [err.error?.message || err.message]);
}
});
}
/**
* Submits a clerk double-entry request (maker-checker, PENDING until manager approves).
* Disbursement: 1 DR [GL] source → N CR savings/current/GL destinations.
@@ -2811,16 +3198,23 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
const f = cfg.matchField;
// Exact match first, then suffix/partial match. Some account types (e.g. Share — its
// list endpoint only accepts offset/limit, no accountNo filter at all) always return the
// FULL unfiltered list rather than a server-side-narrowed one, so a partial match can't be
// gated on "exactly one row came back" — it never would be for those types.
const exactMatch = allItems.find((x: any) => x[f] === acNo);
const exactMatch = allItems.find((x: any) => String(x[f] ?? '').trim() === String(acNo).trim());
const partialMatch = !exactMatch
? (allItems.find((x: any) => x[f]?.endsWith(acNo)) ?? allItems.find((x: any) => x[f]?.includes(acNo)))
? (allItems.find((x: any) => String(x[f] ?? '').endsWith(acNo)) ??
allItems.find((x: any) => String(x[f] ?? '').includes(acNo)))
: null;
const match = exactMatch ?? partialMatch ?? null;
const idMatch =
!exactMatch && !partialMatch && acTyp === 'GL'
? allItems.find((x: any) => String(x.id) === String(acNo))
: null;
const match = exactMatch ?? partialMatch ?? idMatch ?? null;
if (match?.id) {
const matchedGlCode = match[f] ?? acNo;
if (this.selectedRow && this.selectedRow.typ === 'DD' && match.glCode) {
this.selectedRow.acNo = match.glCode;
}
const clientIdFromSearch = acTyp !== 'GL' && match.clientId != null ? String(match.clientId) : null;
this.fetchAllDetails(match.id, match[f] ?? acNo, cfg, token, acTyp, clientIdFromSearch);
this.fetchAllDetails(match.id, matchedGlCode, cfg, token, acTyp, clientIdFromSearch);
return;
}
this.loadingAccount = false;
@@ -3048,7 +3442,7 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
return false;
}
const returnedId = acc.glCode ?? acc.accountNo;
if (returnedId && returnedId !== acNo) {
if (returnedId && returnedId !== acNo && String(acc.id) !== acNo) {
this.loadingAccount = false;
this.accountError = 'Account mismatch. Please re-enter account number and try again.';
return false;
@@ -3294,7 +3688,6 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
{ label: 'NEFT Facility', active: !!f.neftFacility },
{ label: 'RTGS Facility', active: !!f.rtgsFacility },
{ label: 'Inter-Bank Cap', active: !!f.interBankTotalCapFacility },
{ label: 'Withdrawal', active: !!f.withdrawalFacility },
{ label: 'Deposit (KYC)', active: !!f.depositFacilityKyc },
{ label: 'Virtual Debit Card', active: !!f.virtualDebitCard },
{ label: 'OD', active: !!this.accountDetails?.allowOverdraft }
@@ -4579,6 +4972,10 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
confirmSubmit(): void {
this.showConfirmDialog = false;
const row = this.selectedRow!;
if (row.typ === 'DD') {
this.openIssueWalkinDd(row);
return;
}
const amount = this.isDdCashGlRow(row) ? this.ddTotalPayableFor(row) : (row.amount ?? this.denominationTotal);
const isDeposit = row.dbCr === 'Cr';
const command = isDeposit ? 'deposit' : 'withdrawal';