feat(loan-origination): guarantor share-account verification, committee resolution gating, legal doc uploads, CIC score-detection fixes, and interest/share rate chart
- 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:
@@ -1879,6 +1879,22 @@
|
||||
<div class="pg-card">
|
||||
<div class="pos-section-label">Loan Committee Approval — {{ sanctioningAuthorityOf(a) }}</div>
|
||||
<div class="loan-action-card">
|
||||
<div class="loi-cic-subsection">
|
||||
<div class="loi-cic-subsection-title">Full Application Report — For the Committee</div>
|
||||
<p class="pg-sub npa-report-note">
|
||||
Everything captured on this application — KYC, CIC score, Field Investigation, property valuation, agent
|
||||
report, meeting and stage history — as one PDF for every committee member to review before deciding.
|
||||
</p>
|
||||
<div class="loan-action-row">
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="printApplicationSummary(a)">
|
||||
🖨️ Print Full Report
|
||||
</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="downloadApplicationSummary(a)">
|
||||
⬇️ Download Full Report
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="pg-sub npa-report-note">Reports Required — present below reports, then complete.</p>
|
||||
<div class="loan-checklist">
|
||||
<label class="loan-checklist-item">
|
||||
@@ -1917,12 +1933,27 @@
|
||||
<span>Committee Remarks</span>
|
||||
<input type="text" [(ngModel)]="actionRemarks" placeholder="Remarks" />
|
||||
</label>
|
||||
<div class="inv-form-grid">
|
||||
<label class="inv-form-field">
|
||||
<span>Resolution No.</span>
|
||||
<input type="text" [(ngModel)]="a.committeeResolutionNo" placeholder="e.g. LC/2026/0042" />
|
||||
</label>
|
||||
<label class="inv-form-field">
|
||||
<span>Resolution Date</span>
|
||||
<input type="text" [(ngModel)]="a.committeeResolutionDate" placeholder="DD-MM-YYYY" />
|
||||
</label>
|
||||
</div>
|
||||
@if (committeeReportsReady(a) && !committeeCanApprove(a)) {
|
||||
<p class="loi-kyc-error">
|
||||
Enter the Resolution No. and Resolution Date the committee passed this decision under before approving.
|
||||
</p>
|
||||
}
|
||||
<div class="loan-action-row">
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn pg-btn--success"
|
||||
[disabled]="!committeeReportsReady(a)"
|
||||
(click)="approveStage(a, 'creditCommitteeApproval', 'Sanctioning Authority')"
|
||||
[disabled]="!committeeCanApprove(a)"
|
||||
(click)="approveCommitteeStage(a)"
|
||||
>
|
||||
Approve
|
||||
</button>
|
||||
@@ -1973,10 +2004,65 @@
|
||||
</label>
|
||||
</div>
|
||||
@if (a.guarantorRequired) {
|
||||
<label class="inv-form-field">
|
||||
<span>Guarantor Name</span>
|
||||
<input type="text" [(ngModel)]="a.guarantorName" />
|
||||
</label>
|
||||
<div class="loi-cic-subsection">
|
||||
<div class="loi-cic-subsection-title">Guarantor — Must Be an Existing Share Member</div>
|
||||
@if (!a.guarantorClientId) {
|
||||
<label class="inv-form-field">
|
||||
<span>Search Existing Client</span>
|
||||
<input
|
||||
type="text"
|
||||
[(ngModel)]="guarantorSearchText"
|
||||
(ngModelChange)="onGuarantorSearch($event)"
|
||||
placeholder="Name, account no., mobile..."
|
||||
/>
|
||||
</label>
|
||||
@if (searchingGuarantor) {
|
||||
<p class="loi-search-hint">Searching…</p>
|
||||
} @else if (guarantorSearchText.length >= 2 && guarantorSearchResults.length === 0) {
|
||||
<p class="loi-search-hint">No clients found.</p>
|
||||
}
|
||||
@if (guarantorSearchResults.length > 0) {
|
||||
<div class="loi-search-results">
|
||||
@for (client of guarantorSearchResults; track client.id) {
|
||||
<button type="button" class="loi-search-row" (click)="selectGuarantor(a, client)">
|
||||
<span class="loi-search-name">{{ client.displayName }}</span>
|
||||
<span class="loi-search-meta">{{ client.accountNo ?? client.officeName ?? '' }}</span>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<p class="loi-kyc-error">
|
||||
The guarantor must already be a bank client with an active share account — search and select them
|
||||
above, a name alone isn't enough.
|
||||
</p>
|
||||
} @else {
|
||||
<label class="inv-form-field">
|
||||
<span>Guarantor</span>
|
||||
<div class="loi-selected-client">
|
||||
<span>{{ a.guarantorName }}</span>
|
||||
<button type="button" class="pg-btn" (click)="clearGuarantor(a)">Change</button>
|
||||
</div>
|
||||
</label>
|
||||
@if (loadingGuarantorShareCheck) {
|
||||
<p class="loi-search-hint">Checking share membership…</p>
|
||||
} @else if (a.guarantorHasShareAccount) {
|
||||
<span class="loan-badge loan-badge--verified">Share Member — Verified</span>
|
||||
<div class="loi-share-account-list">
|
||||
@for (sa of guarantorShareAccounts; track sa.id) {
|
||||
<button type="button" class="loi-share-account-chip" (click)="viewShareAccountDetails(sa.id)">
|
||||
{{ sa.accountNo }} — {{ sa.productName }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<span class="loan-badge loan-badge--missing">No Share Account Found</span>
|
||||
<p class="loi-kyc-error">
|
||||
This client has no active share account — a guarantor must already be a bank member. Pick a
|
||||
different guarantor.
|
||||
</p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<label class="inv-form-field">
|
||||
<span>Risk Remarks</span>
|
||||
@@ -2028,6 +2114,98 @@
|
||||
<input type="checkbox" [(ngModel)]="a.fileFilingDone" /> File Filing Done
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@if (a.legalDocuments.length > 0) {
|
||||
<div class="loi-cic-subsection">
|
||||
<div class="loi-legal-doc-header">
|
||||
<div class="loi-cic-subsection-title">Legal Documents</div>
|
||||
<span class="loi-legal-doc-progress">
|
||||
{{ legalDocsSubmittedCount(a) }} / {{ a.legalDocuments.length }} submitted
|
||||
</span>
|
||||
</div>
|
||||
<div class="loi-legal-doc-grid">
|
||||
@for (doc of a.legalDocuments; track doc.key) {
|
||||
<div class="loi-legal-doc-card" [class.loi-legal-doc-card--submitted]="doc.submitted">
|
||||
<div class="loi-legal-doc-title-row">
|
||||
<span class="loi-legal-doc-icon">{{ legalDocIcon(doc.key) }}</span>
|
||||
<span class="loi-legal-doc-title">{{ doc.label }}</span>
|
||||
@if (doc.custom) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-doc-remove"
|
||||
title="Remove this document"
|
||||
(click)="removeCustomLegalDoc(a, doc)"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="loi-kyc-card-files">
|
||||
<label class="pg-btn pg-btn--ghost loi-legal-doc-upload-btn">
|
||||
+ Add File(s)
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*,application/pdf"
|
||||
multiple
|
||||
hidden
|
||||
(change)="onLegalDocFile(doc, $event)"
|
||||
/>
|
||||
</label>
|
||||
@for (capture of legalDocCapture[doc.key]; track $index) {
|
||||
<div class="loi-legal-doc-thumb-wrap">
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewNewSingleDoc(capture.file)"
|
||||
>
|
||||
@if (capture.previewUrl) {
|
||||
<img [src]="capture.previewUrl" alt="{{ doc.label }} {{ $index + 1 }}" />
|
||||
} @else {
|
||||
<span class="loi-kyc-thumb-label">View file</span>
|
||||
}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="loi-legal-doc-thumb-remove"
|
||||
title="Remove this file"
|
||||
(click)="removeLegalDocFile(doc, $index)"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (doc.submitted) {
|
||||
<span class="loan-badge loan-badge--verified">
|
||||
✓ Submitted
|
||||
@if (legalDocCapture[doc.key]?.length) {
|
||||
· {{ legalDocCapture[doc.key].length }} file{{
|
||||
legalDocCapture[doc.key].length > 1 ? 's' : ''
|
||||
}}
|
||||
this session
|
||||
}
|
||||
</span>
|
||||
} @else {
|
||||
<span class="loan-badge loan-badge--missing">Missing</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="loan-action-row">
|
||||
<input
|
||||
type="text"
|
||||
[(ngModel)]="newLegalDocLabel"
|
||||
placeholder="Add More Doc"
|
||||
class="loi-custom-doc-input"
|
||||
/>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="addCustomLegalDoc(a)">
|
||||
+ Add Custom Document
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<label class="inv-form-field">
|
||||
<span>Agreement Signing Slot</span>
|
||||
<input type="text" [(ngModel)]="a.agreementSigningSlot" placeholder="DD-MM-YYYY" />
|
||||
|
||||
Reference in New Issue
Block a user