feat: add KYC document renewal alerts, Aadhaar/PAN exceptions, and AML recurring alerts UI; remove old journal AML marking
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-08-11 11:11:18 +05:30
parent 01e86173c1
commit 7b895a0e2b
77 changed files with 10046 additions and 1130 deletions
@@ -62,6 +62,30 @@ export class TdsInterestReportComponent implements OnInit {
/** Which customer's quarter-wise detail is open. */
expandedId: string | null = null;
exporting = false;
selectedTab: 'paying' | 'not-paying' = 'paying';
/** Rows per page; 0 shows the whole list. */
readonly pageSizeOptions = [
25,
50,
100,
0
];
pageSize = 25;
pageIndex = 0;
/**
* Row and summary computation is memoised against the filter, because every getter below is
* read many times per change-detection cycle (the KPI block alone reads `summary` a dozen
* times) and recomputing the whole book on each read is what made this table slow enough to
* need paging in the first place.
*/
private rowsCacheKey = '';
private allRows: TdsInterestRow[] = [];
private paying: TdsInterestRow[] = [];
private notPaying: TdsInterestRow[] = [];
private summaryCacheKey = '';
private cachedSummary: TdsInterestSummary | null = null;
ngOnInit(): void {
// A branch login sees only its own book and cannot widen the scope.
@@ -94,12 +118,107 @@ export class TdsInterestReportComponent implements OnInit {
return this.filter.branchCode ? glBranchName(this.filter.branchCode) : 'All branches';
}
/** Recomputes the book only when the filter actually changed. Side-effect free. */
private ensureRows(): void {
const key = JSON.stringify(this.filter);
if (key === this.rowsCacheKey) {
return;
}
this.rowsCacheKey = key;
this.allRows = tdsInterestRows(this.filter);
this.paying = this.allRows.filter((r) => r.schedule.totalTds > 0);
this.notPaying = this.allRows.filter((r) => r.schedule.totalTds === 0);
}
get rows(): TdsInterestRow[] {
return tdsInterestRows(this.filter);
this.ensureRows();
return this.allRows;
}
get payingRows(): TdsInterestRow[] {
this.ensureRows();
return this.paying;
}
get notPayingRows(): TdsInterestRow[] {
this.ensureRows();
return this.notPaying;
}
get summary(): TdsInterestSummary {
return tdsInterestSummary(this.filter);
const key = JSON.stringify(this.filter);
if (key !== this.summaryCacheKey || !this.cachedSummary) {
this.summaryCacheKey = key;
this.cachedSummary = tdsInterestSummary(this.filter);
}
return this.cachedSummary;
}
/** Every row of the tab on screen, before paging. */
get visibleRows(): TdsInterestRow[] {
return this.selectedTab === 'paying' ? this.payingRows : this.notPayingRows;
}
get pageCount(): number {
if (!this.pageSize) {
return 1;
}
return Math.max(1, Math.ceil(this.visibleRows.length / this.pageSize));
}
/**
* Clamped rather than assigned, so a filter that shrinks the list can never leave the table
* rendering an out-of-range page, and nothing mutates state during change detection.
*/
get currentPage(): number {
return Math.min(this.pageIndex, this.pageCount - 1);
}
/** The slice actually rendered. */
get pagedRows(): TdsInterestRow[] {
const rows = this.visibleRows;
if (!this.pageSize) {
return rows;
}
const start = this.currentPage * this.pageSize;
return rows.slice(start, start + this.pageSize);
}
get rangeStart(): number {
if (!this.visibleRows.length) {
return 0;
}
return this.pageSize ? this.currentPage * this.pageSize + 1 : 1;
}
get rangeEnd(): number {
const total = this.visibleRows.length;
return this.pageSize ? Math.min(total, (this.currentPage + 1) * this.pageSize) : total;
}
goToPage(page: number): void {
this.pageIndex = Math.max(0, Math.min(page, this.pageCount - 1));
// The open quarter-wise detail belongs to a row that is no longer on screen.
this.expandedId = null;
}
/** Keeps the first row currently on screen in view when the page size changes. */
onPageSizeChange(size: number): void {
const anchor = this.currentPage * (this.pageSize || 1);
this.pageSize = Number(size);
this.pageIndex = this.pageSize ? Math.floor(anchor / this.pageSize) : 0;
this.expandedId = null;
}
/** A different filter is a different list, so paging starts over. */
onFilterChanged(): void {
this.pageIndex = 0;
this.expandedId = null;
}
selectTab(tab: 'paying' | 'not-paying'): void {
this.selectedTab = tab;
this.onFilterChanged();
}
get hasFilters(): boolean {
@@ -121,11 +240,13 @@ export class TdsInterestReportComponent implements OnInit {
crossedOnly: false,
nearThresholdOnly: false
};
this.onFilterChanged();
}
/** Jump straight to the customers worth chasing a declaration from. */
showNearThreshold(): void {
this.filter = { ...this.filter, crossedOnly: false, nearThresholdOnly: true };
this.onFilterChanged();
}
toggleExpanded(row: TdsInterestRow): void {