feat(delinquency): maker-checker UI for Delinquency Ranges and Buckets
Checker inbox now resolves DELINQUENCY_RANGE/DELINQUENCY_BUCKET entries to readable diffs instead of raw JSON, and each bucket/range screen shows its own pending-approval state via a new pending-requests panel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,9 @@ const ENTITY_FETCHERS: Record<string, (id: string, s: EntityFetchServices) => Ob
|
||||
FIXEDDEPOSITPRODUCT: (id, s) => s.products.getFixedDepositProductAndTemplate(id),
|
||||
RECURRINGDEPOSITPRODUCT: (id, s) => s.products.getRecurringDepositProduct(id, true),
|
||||
CHARGE: (id, s) => s.products.getCharge(id, true),
|
||||
// SMA / NPA classification master. The bucket is read as-is; the dialog resolves its range ids to names.
|
||||
DELINQUENCY_RANGE: (id, s) => s.products.getDelinquencyRange(id),
|
||||
DELINQUENCY_BUCKET: (id, s) => s.products.getDelinquencyBucket(id),
|
||||
|
||||
// ---- Accounts ----------------------------------------------------------------
|
||||
SAVINGSACCOUNT: (id, s) => s.savings.getSavingsAccountData(id),
|
||||
@@ -199,6 +202,12 @@ const DELETE_SUMMARY_KEYS: Record<string, string[]> = {
|
||||
'email',
|
||||
'officeName'
|
||||
],
|
||||
DELINQUENCY_RANGE: [
|
||||
'classification',
|
||||
'minimumAgeDays',
|
||||
'maximumAgeDays'
|
||||
],
|
||||
DELINQUENCY_BUCKET: ['name'],
|
||||
GLACCOUNT: [
|
||||
'name',
|
||||
'glCode',
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import * as _ from 'lodash';
|
||||
import { forkJoin } from 'rxjs';
|
||||
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
|
||||
/** Custom Services */
|
||||
@@ -417,6 +418,9 @@ export class ViewCheckerInboxComponent {
|
||||
if ((detail?.entityName ?? '').toUpperCase() === 'GLACCOUNT') {
|
||||
this.resolveGlAccountCreateLabels(detail, this.jsondata);
|
||||
}
|
||||
if ((detail?.entityName ?? '').toUpperCase() === 'DELINQUENCY_BUCKET') {
|
||||
this.resolveDelinquencyBucketCreateRanges(detail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,6 +433,10 @@ export class ViewCheckerInboxComponent {
|
||||
* entity showed before.
|
||||
*/
|
||||
private loadUpdateChanges(detail: any): void {
|
||||
if ((detail?.entityName ?? '').toUpperCase() === 'DELINQUENCY_BUCKET') {
|
||||
this.loadDelinquencyBucketChanges(detail);
|
||||
return;
|
||||
}
|
||||
const request$ = fetchCurrentEntity(detail?.entityName, detail?.resourceId, this.entityFetchServices);
|
||||
if (!request$) {
|
||||
return;
|
||||
@@ -451,6 +459,78 @@ export class ViewCheckerInboxComponent {
|
||||
});
|
||||
}
|
||||
|
||||
/** One line per range for a delinquency bucket, e.g. 'SMA-1 (31 - 60 days)', oldest range first. */
|
||||
private delinquencyRangeNames(ids: any[], rangesById: Map<number, any>): string {
|
||||
return (ids ?? [])
|
||||
.map((entry) => rangesById.get(Number(entry?.id ?? entry)) ?? { id: Number(entry?.id ?? entry) })
|
||||
.sort((a, b) => (a.minimumAgeDays ?? 0) - (b.minimumAgeDays ?? 0))
|
||||
.map((range) =>
|
||||
range.classification
|
||||
? `${range.classification} (${range.minimumAgeDays} - ${range.maximumAgeDays ?? 'no limit'} days)`
|
||||
: `Range #${range.id}`
|
||||
)
|
||||
.join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delinquency bucket UPDATE: the payload lists range ids, which mean nothing to a checker, and the stored
|
||||
* bucket lists range objects, so a plain field diff would show two unreadable lists. Both sides are turned
|
||||
* into the same readable range names first, so only a real difference is reported as OLD -> NEW.
|
||||
*/
|
||||
private loadDelinquencyBucketChanges(detail: any): void {
|
||||
this.comparing = true;
|
||||
forkJoin({
|
||||
current: this.productsService.getDelinquencyBucket(detail?.resourceId),
|
||||
ranges: this.productsService.getDelinquencyRanges()
|
||||
}).subscribe({
|
||||
next: ({ current, ranges }: { current: any; ranges: any }) => {
|
||||
if (!this.isCurrent(detail)) {
|
||||
return;
|
||||
}
|
||||
this.comparing = false;
|
||||
const rangesById = new Map<number, any>(
|
||||
(ranges ?? []).map((range: any) => [
|
||||
Number(range.id),
|
||||
range
|
||||
])
|
||||
);
|
||||
const submitted = {
|
||||
...(this.jsondata ?? {}),
|
||||
ranges: this.delinquencyRangeNames(this.jsondata?.ranges, rangesById)
|
||||
};
|
||||
const existing = { ...(current ?? {}), ranges: this.delinquencyRangeNames(current?.ranges, rangesById) };
|
||||
this.changes = diffCommandAgainstEntity(submitted, existing);
|
||||
this.showChanges = true;
|
||||
},
|
||||
error: () => {
|
||||
this.comparing = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Delinquency bucket CREATE: show the chosen ranges by name instead of their ids. */
|
||||
private resolveDelinquencyBucketCreateRanges(detail: any): void {
|
||||
this.productsService.getDelinquencyRanges().subscribe({
|
||||
next: (ranges: any) => {
|
||||
if (!this.isCurrent(detail)) {
|
||||
return;
|
||||
}
|
||||
const rangesById = new Map<number, any>(
|
||||
(ranges ?? []).map((range: any) => [
|
||||
Number(range.id),
|
||||
range
|
||||
])
|
||||
);
|
||||
const resolved = {
|
||||
...(this.jsondata ?? {}),
|
||||
ranges: this.delinquencyRangeNames(this.jsondata?.ranges, rangesById)
|
||||
};
|
||||
this.createSections = this.buildCreateSections(resolved);
|
||||
this.commandFields = this.buildCommandFields(resolved);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loan Base Interest Rate: every submission is a CREATE of a whole new version (16 loan types x 10
|
||||
* slabs plus CIBIL bands), so the raw payload hides the handful of rates the maker actually moved.
|
||||
|
||||
Reference in New Issue
Block a user