feat(loan-origination): guarantor share-account verification, committee resolution gating, legal doc uploads, CIC score-detection fixes, and interest/share rate chart
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

- Guarantor must be a real existing client with a verified active share account, with clickable share-account chips
- Loan Committee Approval now requires Resolution No./Date plus a full application report before approving
- Loan Committee gets its own pre-filtered pipeline view via a menu link and stage query param
- Legal Approval stage gets a multi-file document upload checklist with add-more support
- CIC PDF parser: item-level score detection before line-based fallback, fixing font-size-driven line-merging misdetection
- New Interest & Share Rate Chart tab: per-product interest/tenure bands and per-amount-slab Share Capital linkage requirements
This commit is contained in:
2026-08-14 14:49:22 +05:30
parent 1d1e76497c
commit 2d93f80d50
8 changed files with 773 additions and 38 deletions
@@ -29,6 +29,7 @@ import {
PRODUCT_TARGET_PCT,
PRODUCT_TYPE_LABELS,
ProductType,
SHARE_LINKAGE_SLABS,
STAGE_LABELS,
STAGE_ORDER,
STATUS_LABELS,
@@ -81,7 +82,17 @@ interface MixTargetRow {
isRbiMandated: boolean;
}
type ViewId = 'pipeline' | 'new' | 'channel' | 'bc' | 'share' | 'target';
interface AmountSlabRow {
minAmt: number;
maxAmt: number | null;
sharePercent: number;
/** Share Capital required at the slab's lower and upper bound, so the rupee amount is visible
* without officers having to do the multiplication themselves. */
shareAtMin: number;
shareAtMax: number | null;
}
type ViewId = 'pipeline' | 'new' | 'channel' | 'bc' | 'share' | 'target' | 'rates';
/**
* Loan Origination pipeline — every application currently moving through the sanctioning
@@ -115,6 +126,7 @@ export class LoanPipelineComponent implements OnInit {
readonly channelLabels = CHANNEL_LABELS;
readonly branches = BRANCHES;
readonly products = ORIGINATION_PRODUCTS;
readonly shareLinkageSlabs = SHARE_LINKAGE_SLABS;
asOnDate = todayDDMMYYYY();
activeView: ViewId = 'pipeline';
@@ -185,11 +197,20 @@ export class LoanPipelineComponent implements OnInit {
'channel',
'bc',
'share',
'target'
'target',
'rates'
].includes(requestedView)) {
this.activeView = requestedView;
}
// Lets a menu link (e.g. "Loan Committee") land here pre-filtered to just the applications
// waiting on that one stage, reusing this same pipeline list/filter instead of a separate
// queue view -- same pattern as the `view` query param above.
const requestedStage = this.route.snapshot.queryParamMap.get('stage') as OriginationStage | null;
if (requestedStage && this.stageOrder.includes(requestedStage)) {
this.stageFilter = requestedStage;
}
const creds = this.auth.getCredentials();
this.isHeadOffice = this.auth.isHeadOfficeUser();
this.currentOfficeName = creds?.officeName || 'Branch Office';
@@ -427,6 +448,22 @@ export class LoanPipelineComponent implements OnInit {
});
}
// -------------------- Interest & Share Linkage Reference --------------------
/** Illustrative total interest over the product's own tenure band, simple-interest approximation
* (principal x rate x years) -- flagged as illustrative in the template since actual repayment
* is on a reducing balance and varies by amount/tenure chosen at sanction. */
illustrativeInterest(amount: number, ratePct: number, tenureMonths: number): number {
return amount * (ratePct / 100) * (tenureMonths / 12);
}
get shareLinkageSlabRows(): AmountSlabRow[] {
return this.shareLinkageSlabs.map((slab) => ({
...slab,
shareAtMin: slab.minAmt * (slab.sharePercent / 100),
shareAtMax: slab.maxAmt !== null ? slab.maxAmt * (slab.sharePercent / 100) : null
}));
}
get channelTargetRows(): MixTargetRow[] {
const total = this.disbursedInScope.reduce((s, a) => s + a.requestedAmount, 0);
const channels: Channel[] = [