feat(cashier): enhance transaction entry TYPE/TXN TP options, auto-search, and account type configs
- Replace TYPE dropdown options with Withdrawal Slip, Cheque, Disbursement, Enter Bank Transfer, DD - Move TXN TP. (payment type) into Classification row between BRANCH and AC. TP. - Add GL and LA account types; fix SHR endpoint to /accounts/share - Auto-search A/C NO. on 4+ digits with 400ms debounce - Auto-fill ECS A/C NO. from savingsAccountNumber after account load - Accept suffix/partial accountNo match from list API - Disable broken global /search fallback across all account types - Increase field widths: TYPE 160px, TXN TP. 160px, CHEQUE NO. 165px, TOK. NO. 120px Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
40a436dabe
commit
010adcb44f
@@ -10,8 +10,8 @@ import { Component, OnInit, OnDestroy, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { forkJoin, of } from 'rxjs';
|
||||
import { catchError, debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
||||
import { forkJoin, of, Subject } from 'rxjs';
|
||||
import { MatDialog, MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { CashierService } from '../cashier.service';
|
||||
import { AuthenticationService } from '../../core/authentication/authentication.service';
|
||||
@@ -659,6 +659,7 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Search token — cancels stale async results
|
||||
private searchToken = 0;
|
||||
private acNoInput$ = new Subject<{ row: TxnRow; index: number; value: string }>();
|
||||
|
||||
// Joint holder expanded details (keyed by index)
|
||||
expandedHolder: number | null = null;
|
||||
@@ -710,7 +711,8 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
'FD',
|
||||
'RD',
|
||||
'SHR',
|
||||
'CC'
|
||||
'GL',
|
||||
'LA'
|
||||
];
|
||||
dbCrOptions = [
|
||||
'Dr',
|
||||
@@ -753,6 +755,18 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
.pipe(catchError(() => of([])))
|
||||
.subscribe((types: any[]) => (this.paymentTypes = types ?? []));
|
||||
|
||||
this.acNoInput$
|
||||
.pipe(
|
||||
debounceTime(400),
|
||||
distinctUntilChanged((a, b) => a.value === b.value && a.row === b.row)
|
||||
)
|
||||
.subscribe(({ row, index, value }) => {
|
||||
if (value.trim().length >= 4) {
|
||||
this.selectedRow = row;
|
||||
this.loadAccountDetails(value.trim(), row.acTyp);
|
||||
}
|
||||
});
|
||||
|
||||
if (this.isClerk) {
|
||||
const saved = this.clerkUiState.snapshot.savedAccount;
|
||||
if (saved) {
|
||||
@@ -1022,6 +1036,11 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
this.loadAccountDetails(row.acNo.trim(), row.acTyp);
|
||||
}
|
||||
|
||||
onAcNoInput(row: TxnRow, index: number, value: string): void {
|
||||
row.acNo = value;
|
||||
this.acNoInput$.next({ row, index, value });
|
||||
}
|
||||
|
||||
onRowSelect(row: TxnRow, index: number): void {
|
||||
this.selectedRow = row;
|
||||
// Only reload if account number changed or nothing is loaded yet
|
||||
@@ -1055,32 +1074,24 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Step 1: search by accountNo on the correct endpoint for this account type
|
||||
this.http
|
||||
.get<any>(`${cfg.list}?accountNo=${encodeURIComponent(acNo)}&offset=0&limit=1`)
|
||||
.get<any>(`${cfg.list}?accountNo=${encodeURIComponent(acNo)}&offset=0&limit=${cfg.maxLimit}`)
|
||||
.pipe(catchError(() => of(null)))
|
||||
.subscribe((res: any) => {
|
||||
if (token !== this.searchToken) return; // stale result — newer search started
|
||||
const allItems: any[] = res?.pageItems ?? (Array.isArray(res) ? res : []);
|
||||
// Find exact match by accountNo — prevents taking wrong first item when API
|
||||
// returns all accounts as an array (e.g. recurringdepositaccounts)
|
||||
const match = allItems.find((x: any) => x.accountNo === acNo) ?? allItems[0];
|
||||
// Exact match first, then suffix/partial match (backend may return by last-N-digits)
|
||||
const exactMatch = allItems.find((x: any) => x.accountNo === acNo);
|
||||
const partialMatch =
|
||||
!exactMatch && allItems.length === 1
|
||||
? allItems.find((x: any) => x.accountNo?.endsWith(acNo) || x.accountNo?.includes(acNo))
|
||||
: null;
|
||||
const match = exactMatch ?? partialMatch ?? null;
|
||||
if (match?.id) {
|
||||
this.fetchAllDetails(match.id, acNo, cfg, token, acTyp);
|
||||
this.fetchAllDetails(match.id, match.accountNo, cfg, token, acTyp);
|
||||
return;
|
||||
}
|
||||
// Fallback: global search
|
||||
this.http
|
||||
.get<any>(`/search?query=${encodeURIComponent(acNo)}&resource=${cfg.search}`)
|
||||
.pipe(catchError(() => of([])))
|
||||
.subscribe((found: any) => {
|
||||
if (token !== this.searchToken) return;
|
||||
const list: any[] = Array.isArray(found) ? found : [];
|
||||
if (list.length > 0 && list[0].entityId) {
|
||||
this.fetchAllDetails(list[0].entityId, acNo, cfg, token, acTyp);
|
||||
} else {
|
||||
this.loadingAccount = false;
|
||||
this.accountError = 'Account not found for: ' + acNo;
|
||||
}
|
||||
});
|
||||
this.loadingAccount = false;
|
||||
this.accountError = 'Account not found for: ' + acNo;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1093,10 +1104,52 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
siAccountType: number;
|
||||
txnBase: string;
|
||||
txnSearchSupported: boolean;
|
||||
maxLimit: number;
|
||||
skipGlobalSearch: boolean;
|
||||
} {
|
||||
const t = acTyp?.toUpperCase();
|
||||
if (t === 'CA' || t === 'CB' || t === 'CC') {
|
||||
// Current account: custom /transactions/search endpoint added to backend
|
||||
if (t === 'LA') {
|
||||
return {
|
||||
list: '/loans',
|
||||
detail: '/loans',
|
||||
nominee: 'LA',
|
||||
search: 'loan',
|
||||
siAccountType: 1,
|
||||
txnBase: '/loans',
|
||||
txnSearchSupported: true,
|
||||
maxLimit: 1,
|
||||
skipGlobalSearch: true
|
||||
};
|
||||
}
|
||||
if (t === 'SHR') {
|
||||
// Backend ignores accountNo filter on list — fetch all and match client-side
|
||||
// Global /search?resource=share returns 500 — skip it
|
||||
return {
|
||||
list: '/accounts/share',
|
||||
detail: '/accounts/share',
|
||||
nominee: 'SHR',
|
||||
search: 'share',
|
||||
siAccountType: 4,
|
||||
txnBase: '/accounts/share',
|
||||
txnSearchSupported: false,
|
||||
maxLimit: 500,
|
||||
skipGlobalSearch: true
|
||||
};
|
||||
}
|
||||
if (t === 'GL') {
|
||||
return {
|
||||
list: '/glaccounts',
|
||||
detail: '/glaccounts',
|
||||
nominee: 'GL',
|
||||
search: 'glaccount',
|
||||
siAccountType: 0,
|
||||
txnBase: '/glaccounts',
|
||||
txnSearchSupported: false,
|
||||
maxLimit: 500,
|
||||
skipGlobalSearch: true
|
||||
};
|
||||
}
|
||||
if (t === 'CA' || t === 'CB') {
|
||||
return {
|
||||
list: '/currentaccounts',
|
||||
detail: '/currentaccounts',
|
||||
@@ -1104,11 +1157,12 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
search: 'currentaccount',
|
||||
siAccountType: 3,
|
||||
txnBase: '/currentaccounts',
|
||||
txnSearchSupported: true
|
||||
txnSearchSupported: true,
|
||||
maxLimit: 1,
|
||||
skipGlobalSearch: true
|
||||
};
|
||||
}
|
||||
if (t === 'FD') {
|
||||
// FD stored in savings table → use /savingsaccounts for transaction search
|
||||
return {
|
||||
list: '/savingsaccounts',
|
||||
detail: '/fixeddepositaccounts',
|
||||
@@ -1116,11 +1170,12 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
search: 'fixeddeposit',
|
||||
siAccountType: 2,
|
||||
txnBase: '/savingsaccounts',
|
||||
txnSearchSupported: true
|
||||
txnSearchSupported: true,
|
||||
maxLimit: 1,
|
||||
skipGlobalSearch: true
|
||||
};
|
||||
}
|
||||
if (t === 'RD') {
|
||||
// RD stored in savings table → use /savingsaccounts for transaction search
|
||||
return {
|
||||
list: '/savingsaccounts',
|
||||
detail: '/recurringdepositaccounts',
|
||||
@@ -1128,10 +1183,12 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
search: 'recurringdeposit',
|
||||
siAccountType: 2,
|
||||
txnBase: '/savingsaccounts',
|
||||
txnSearchSupported: true
|
||||
txnSearchSupported: true,
|
||||
maxLimit: 1,
|
||||
skipGlobalSearch: true
|
||||
};
|
||||
}
|
||||
// SD — savings with full search support
|
||||
// SD — savings
|
||||
return {
|
||||
list: '/savingsaccounts',
|
||||
detail: '/savingsaccounts',
|
||||
@@ -1139,7 +1196,9 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
search: 'saving',
|
||||
siAccountType: 2,
|
||||
txnBase: '/savingsaccounts',
|
||||
txnSearchSupported: true
|
||||
txnSearchSupported: true,
|
||||
maxLimit: 1,
|
||||
skipGlobalSearch: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1182,6 +1241,10 @@ export class TransactionEntryComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
this.accountDetails = acc;
|
||||
this.jointHolders = acc.jointHolders ?? [];
|
||||
// Auto-fill ECS A/C NO. with savings account number (SHR) or account number
|
||||
if (this.selectedRow) {
|
||||
this.selectedRow.ecsAcNo = acc.savingsAccountNumber ?? acc.accountNo ?? '';
|
||||
}
|
||||
if (this.isClerk) this.clerkUiState.setFacilities(this.facilities, acNo, acTyp);
|
||||
// Compute same-day compliance indicators from included transactions
|
||||
this.computeSameDayNeftRtgs(acc.transactions);
|
||||
|
||||
Reference in New Issue
Block a user