feat(loan-origination): persist the sanctioning pipeline to the backend
Replaces the in-memory mock (LOAN_APPLICATIONS array + seeded RNG, reset on every refresh) with real Fineract-backed persistence: a new LoanOriginationService for CRUD plus one API call per stage transition, a loan-fineract-bridge that creates a real loan account (create + approve + disburse) at disbursement for Existing Customers, and file uploads through Fineract's generic document store keyed by a stable checklist slot so "On file"/View controls survive a reload. Also fixes several gaps found while verifying full-cycle persistence: - CIC charge-account selection never saved (missing persist() call) - Co-borrower relationship/monthly income lived in local-only component state instead of on the application, so they reset on refresh - Field Investigation "View Full Details" showed local-session photos only; stored photos are now downloaded and shown as real thumbnails - "Assign Field Officer" was free text; now a dropdown of real CBS loan officers pulled from /staff - site photos and cheque/signature images now open in the same zoomable, pannable lightgallery viewer used elsewhere in the app Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,14 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="pg">
|
||||
<!-- (input)/(change) are bound once here rather than per field: both bubble, and Angular's own
|
||||
ngModel listener sits on the input itself, so by the time this fires the model already holds
|
||||
the new value. Any inline field on this screen — including ones added later — therefore saves
|
||||
without needing its own handler wired up. -->
|
||||
<div class="pg" (input)="onInlineEdit()" (change)="onInlineEdit()">
|
||||
@if (loadingApplication) {
|
||||
<p class="pg-sub">Loading application…</p>
|
||||
}
|
||||
@if (application; as a) {
|
||||
<header class="pg-head">
|
||||
<div>
|
||||
@@ -21,6 +28,18 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="pg-toolbar">
|
||||
@if (uploadingDocs > 0) {
|
||||
<span class="loan-badge loan-badge--progress">
|
||||
Uploading {{ uploadingDocs }} file{{ uploadingDocs > 1 ? 's' : '' }}…
|
||||
</span>
|
||||
} @else if (docUploadFailed) {
|
||||
<span class="loan-badge loan-badge--rejected">File upload failed</span>
|
||||
}
|
||||
@if (saving) {
|
||||
<span class="loan-badge loan-badge--progress">Saving…</span>
|
||||
} @else if (saveFailed) {
|
||||
<span class="loan-badge loan-badge--rejected">Not saved — check connection</span>
|
||||
}
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="goBack()">‹ Back to Pipeline</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -86,8 +105,8 @@
|
||||
<dt>Co-Borrower</dt>
|
||||
<dd>
|
||||
{{ a.coBorrowerName }}
|
||||
@if (coBorrowerCapture.wanted && coBorrowerCapture.relationship) {
|
||||
({{ coBorrowerCapture.relationship }})
|
||||
@if (a.coBorrowerWanted && a.coBorrowerRelationship) {
|
||||
({{ a.coBorrowerRelationship }})
|
||||
}
|
||||
</dd>
|
||||
</div>
|
||||
@@ -168,6 +187,16 @@
|
||||
<dt>EMI Activation Confirmation</dt>
|
||||
<dd>{{ a.emiActivationConfirmed ? 'Confirmed on ' + a.emiActivationDate : 'Pending' }}</dd>
|
||||
</div>
|
||||
<div class="loan-kv-field">
|
||||
<dt>Core Banking Loan Account</dt>
|
||||
<dd>
|
||||
@if (a.fineractLoanAccountNo) {
|
||||
<span class="loan-badge loan-badge--verified">A/C {{ a.fineractLoanAccountNo }}</span>
|
||||
} @else {
|
||||
<span class="pg-sub">Mock disbursement only — no core banking account</span>
|
||||
}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
@@ -336,12 +365,14 @@
|
||||
(change)="onNewSalariedProofFile(a, $event)"
|
||||
/>
|
||||
</label>
|
||||
@if (newKycCapture.incomeProof.salaried.file) {
|
||||
@if (newKycCapture.incomeProof.salaried.file || hasStoredDoc('KYC:incomeProof:salaried')) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewNewSingleDoc(newKycCapture.incomeProof.salaried.file)"
|
||||
(click)="
|
||||
viewKycSlot(a, newKycCapture.incomeProof.salaried.file, 'KYC:incomeProof:salaried')
|
||||
"
|
||||
>
|
||||
@if (newKycCapture.incomeProof.salaried.previewUrl) {
|
||||
<img [src]="newKycCapture.incomeProof.salaried.previewUrl" alt="Salary proof" />
|
||||
@@ -367,12 +398,12 @@
|
||||
(change)="onNewItrFile(a, $index, $event)"
|
||||
/>
|
||||
</label>
|
||||
@if (doc.file) {
|
||||
@if (doc.file || hasStoredDoc('KYC:incomeProof:itr:' + $index)) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewNewSingleDoc(doc.file)"
|
||||
(click)="viewKycSlot(a, doc.file, 'KYC:incomeProof:itr:' + $index)"
|
||||
>
|
||||
@if (doc.previewUrl) {
|
||||
<img [src]="doc.previewUrl" alt="ITR Year {{ $index + 1 }}" />
|
||||
@@ -436,12 +467,12 @@
|
||||
(change)="onNewBankStatementFile(a, $event)"
|
||||
/>
|
||||
</label>
|
||||
@if (newKycCapture.bankStatement.file) {
|
||||
@if (newKycCapture.bankStatement.file || hasStoredDoc('KYC:bankStatement')) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewNewSingleDoc(newKycCapture.bankStatement.file)"
|
||||
(click)="viewKycSlot(a, newKycCapture.bankStatement.file, 'KYC:bankStatement')"
|
||||
>
|
||||
@if (newKycCapture.bankStatement.previewUrl) {
|
||||
<img [src]="newKycCapture.bankStatement.previewUrl" alt="Bank statement" />
|
||||
@@ -449,12 +480,12 @@
|
||||
<span class="loi-kyc-thumb-label">View file</span>
|
||||
}
|
||||
</button>
|
||||
@if (isCsvFile(newKycCapture.bankStatement.file)) {
|
||||
@if (canReopenBankStatementAnalysis()) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-bsa-reopen"
|
||||
title="Re-open the transaction analysis"
|
||||
(click)="reopenBankStatementAnalysis(newKycCapture.bankStatement.file)"
|
||||
(click)="reopenBankStatementAnalysisFor(a)"
|
||||
>
|
||||
📊 Re-check Analysis
|
||||
</button>
|
||||
@@ -498,12 +529,12 @@
|
||||
(change)="onNewElectricityBillFile(a, $event)"
|
||||
/>
|
||||
</label>
|
||||
@if (newKycCapture.electricityBill.file) {
|
||||
@if (newKycCapture.electricityBill.file || hasStoredDoc('KYC:electricityBill')) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewNewSingleDoc(newKycCapture.electricityBill.file)"
|
||||
(click)="viewKycSlot(a, newKycCapture.electricityBill.file, 'KYC:electricityBill')"
|
||||
>
|
||||
@if (newKycCapture.electricityBill.previewUrl) {
|
||||
<img [src]="newKycCapture.electricityBill.previewUrl" alt="Electricity bill" />
|
||||
@@ -949,12 +980,12 @@
|
||||
/>
|
||||
</label>
|
||||
@if (productDocCapture[doc.key]; as capture) {
|
||||
@if (capture.file) {
|
||||
@if (capture.file || hasStoredDoc('PRODUCT_DOC:' + doc.key)) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewNewSingleDoc(capture.file)"
|
||||
(click)="viewKycSlot(a, capture.file, 'PRODUCT_DOC:' + doc.key)"
|
||||
>
|
||||
@if (capture.previewUrl) {
|
||||
<img [src]="capture.previewUrl" alt="{{ doc.label }}" />
|
||||
@@ -1109,17 +1140,17 @@
|
||||
<div class="loi-cic-subsection">
|
||||
<div class="loi-cic-subsection-title">Co-Borrower (raises household eligibility)</div>
|
||||
<label class="loan-checklist-item">
|
||||
<input type="checkbox" [(ngModel)]="coBorrowerCapture.wanted" />
|
||||
<input type="checkbox" [(ngModel)]="a.coBorrowerWanted" />
|
||||
Add a Co-Borrower to increase eligibility
|
||||
</label>
|
||||
@if (coBorrowerCapture.wanted) {
|
||||
@if (a.coBorrowerWanted) {
|
||||
<label class="inv-form-field">
|
||||
<span>Co-Borrower Name</span>
|
||||
<input type="text" [(ngModel)]="a.coBorrowerName" placeholder="Full name" />
|
||||
</label>
|
||||
<label class="inv-form-field">
|
||||
<span>Relationship with Applicant</span>
|
||||
<select [(ngModel)]="coBorrowerCapture.relationship">
|
||||
<select [(ngModel)]="a.coBorrowerRelationship">
|
||||
<option value="">Select</option>
|
||||
<option value="Father">Father</option>
|
||||
<option value="Mother">Mother</option>
|
||||
@@ -1132,7 +1163,7 @@
|
||||
</label>
|
||||
<label class="inv-form-field">
|
||||
<span>Co-Borrower Monthly Income (₹)</span>
|
||||
<input type="number" [(ngModel)]="coBorrowerCapture.monthlyIncome" placeholder="e.g. 30000" />
|
||||
<input type="number" [(ngModel)]="a.coBorrowerMonthlyIncome" placeholder="e.g. 30000" />
|
||||
</label>
|
||||
<p class="pg-sub npa-report-note">Upload the co-borrower's last 3 months' salary slips:</p>
|
||||
<div class="loi-kyc-card-files">
|
||||
@@ -1150,12 +1181,12 @@
|
||||
(change)="onCoBorrowerSalarySlipFile($index, $event)"
|
||||
/>
|
||||
</label>
|
||||
@if (doc.file) {
|
||||
@if (doc.file || hasStoredDoc('COBORROWER:salarySlip:' + $index)) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewNewSingleDoc(doc.file)"
|
||||
(click)="viewKycSlot(a, doc.file, 'COBORROWER:salarySlip:' + $index)"
|
||||
>
|
||||
@if (doc.previewUrl) {
|
||||
<img [src]="doc.previewUrl" alt="Salary Slip Month {{ $index + 1 }}" />
|
||||
@@ -1180,11 +1211,11 @@
|
||||
</div>
|
||||
<div class="loan-kv-field">
|
||||
<dt>Co-Borrower's Monthly Income</dt>
|
||||
<dd>₹{{ coBorrowerCapture.monthlyIncome | number: '1.0-0' }}</dd>
|
||||
<dd>₹{{ a.coBorrowerMonthlyIncome | number: '1.0-0' }}</dd>
|
||||
</div>
|
||||
<div class="loan-kv-field">
|
||||
<dt>Co-Borrower's EMI Capacity (40%)</dt>
|
||||
<dd>₹{{ coBorrowerEmiCapacity() | number: '1.0-0' }}</dd>
|
||||
<dd>₹{{ coBorrowerEmiCapacity(a) | number: '1.0-0' }}</dd>
|
||||
</div>
|
||||
<div class="loan-kv-field">
|
||||
<dt>Combined Household EMI Capacity</dt>
|
||||
@@ -1197,10 +1228,10 @@
|
||||
</dl>
|
||||
<p class="pg-sub npa-report-note loi-emi-formula">
|
||||
Combined Capacity = (40% × ₹{{ a.monthlyIncome | number: '1.0-0' }} applicant) + (40% × ₹{{
|
||||
coBorrowerCapture.monthlyIncome | number: '1.0-0'
|
||||
a.coBorrowerMonthlyIncome | number: '1.0-0'
|
||||
}}
|
||||
co-borrower) = ₹{{ applicantEmiCapacity(a) | number: '1.0-0' }} + ₹{{
|
||||
coBorrowerEmiCapacity() | number: '1.0-0'
|
||||
coBorrowerEmiCapacity(a) | number: '1.0-0'
|
||||
}}
|
||||
= ₹{{ combinedEmiCapacity(a) | number: '1.0-0' }}. Estimated EMI ₹{{
|
||||
emiAmountFor(a) | number: '1.0-0'
|
||||
@@ -1220,7 +1251,7 @@
|
||||
@if (!affordabilityOk(a)) {
|
||||
<p class="loi-kyc-error">
|
||||
@if (!coBorrowerDetailsComplete(a)) {
|
||||
@if (!coBorrowerCapture.wanted) {
|
||||
@if (!a.coBorrowerWanted) {
|
||||
Check "Add a Co-Borrower" and complete their details to proceed.
|
||||
} @else {
|
||||
Add the co-borrower's name, relationship, monthly income and all 3 salary slips to proceed.
|
||||
@@ -1238,7 +1269,7 @@
|
||||
type="button"
|
||||
class="pg-btn pg-btn--primary"
|
||||
[disabled]="!allKycDone(a)"
|
||||
(click)="proceed(a, 'KYC/OVD documents verified and collected.')"
|
||||
(click)="completeKycStage(a)"
|
||||
>
|
||||
Proceed to CIC / Credit Bureau Check
|
||||
</button>
|
||||
@@ -1252,138 +1283,139 @@
|
||||
<div class="pg-card">
|
||||
<div class="pos-section-label">CIC / Credit Bureau Check — {{ a.cicBureau }}</div>
|
||||
<div class="loan-action-card">
|
||||
@if (a.cibilScore === 0) {
|
||||
<div class="loi-cic-subsection">
|
||||
<div class="loi-cic-subsection-title">Customer Consent — Credit Bureau Check</div>
|
||||
<p class="pg-sub npa-report-note">
|
||||
RBI's Fair Practices Code requires the customer's signed consent before a credit bureau pull. Generate
|
||||
the declaration letter below, get it signed by the customer, then upload the signed copy.
|
||||
</p>
|
||||
<div class="loan-action-row">
|
||||
@if (a.channel === 'BRANCH') {
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="printCicConsentLetter(a)">
|
||||
Print Declaration Letter
|
||||
</button>
|
||||
} @else {
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="downloadCicConsentLetter(a)">
|
||||
Download Declaration Letter
|
||||
</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="shareCicConsentLetter(a)">
|
||||
Share (Email / WhatsApp / Drive…)
|
||||
</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="emailCicConsentLetter(a)">Email</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="whatsappCicConsentLetter(a)">
|
||||
WhatsApp
|
||||
</button>
|
||||
}
|
||||
<label class="pg-btn pg-btn--ghost">
|
||||
{{ cicConsentCapture.file ? 'Signed Declaration ✓' : 'Upload Signed Declaration' }}
|
||||
<input type="file" accept="image/*,application/pdf" hidden (change)="onCicConsentFile(a, $event)" />
|
||||
</label>
|
||||
<!-- Consent and charge stay visible for the whole CIC stage, not just until the score
|
||||
is checked -- same layout either way, so what was collected (and by whom) is never
|
||||
a click away from disappearing once the score card below takes over. -->
|
||||
<div class="loi-cic-subsection">
|
||||
<div class="loi-cic-subsection-title">Customer Consent — Credit Bureau Check</div>
|
||||
<p class="pg-sub npa-report-note">
|
||||
RBI's Fair Practices Code requires the customer's signed consent before a credit bureau pull. Generate
|
||||
the declaration letter below, get it signed by the customer, then upload the signed copy.
|
||||
</p>
|
||||
<div class="loan-action-row">
|
||||
@if (a.channel === 'BRANCH') {
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="printCicConsentLetter(a)">
|
||||
Print Declaration Letter
|
||||
</button>
|
||||
} @else {
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="downloadCicConsentLetter(a)">
|
||||
Download Declaration Letter
|
||||
</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="shareCicConsentLetter(a)">
|
||||
Share (Email / WhatsApp / Drive…)
|
||||
</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="emailCicConsentLetter(a)">Email</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="whatsappCicConsentLetter(a)">
|
||||
WhatsApp
|
||||
</button>
|
||||
}
|
||||
<label class="pg-btn pg-btn--ghost">
|
||||
{{ cicConsentCapture.file ? 'Signed Declaration ✓' : 'Upload Signed Declaration' }}
|
||||
<input type="file" accept="image/*,application/pdf" hidden (change)="onCicConsentFile(a, $event)" />
|
||||
</label>
|
||||
</div>
|
||||
@if (cicConsentCapture.file || hasStoredDoc('CIC:consent')) {
|
||||
<div class="loi-kyc-thumbs">
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewKycSlot(a, cicConsentCapture.file, 'CIC:consent')"
|
||||
>
|
||||
@if (cicConsentCapture.previewUrl) {
|
||||
<img [src]="cicConsentCapture.previewUrl" alt="Signed consent declaration" />
|
||||
} @else {
|
||||
<span class="loi-kyc-thumb-label">View file</span>
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
@if (cicConsentCapture.file) {
|
||||
<div class="loi-kyc-thumbs">
|
||||
}
|
||||
@if (a.cicConsentSigned) {
|
||||
<span class="loan-badge loan-badge--verified">Signed Declaration Uploaded</span>
|
||||
} @else {
|
||||
<span class="loan-badge loan-badge--missing">Awaiting Signed Declaration</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="loi-cic-subsection">
|
||||
<div class="loi-cic-subsection-title">CIC Check Charge — ₹{{ cicCheckCharge }}</div>
|
||||
@if (a.clientId && realDebitableAccounts.length > 0) {
|
||||
<!-- Existing Customer with a real account -- pick which one to debit. This
|
||||
pipeline never posts an actual Fineract transaction, so the "debit" is
|
||||
recorded as a label for audit only. -->
|
||||
<p class="pg-sub npa-report-note">
|
||||
Select which account to debit the ₹{{ cicCheckCharge }} charge from:
|
||||
</p>
|
||||
<div class="loi-kyc-doc-grid">
|
||||
@for (acc of realDebitableAccounts; track acc.id) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="View document"
|
||||
(click)="viewNewSingleDoc(cicConsentCapture.file)"
|
||||
class="loi-kyc-card loi-charge-account"
|
||||
[class.loi-charge-account--selected]="a.cicChargeAccountLabel.startsWith(acc.accountNo)"
|
||||
(click)="selectCicChargeAccount(a, acc)"
|
||||
>
|
||||
@if (cicConsentCapture.previewUrl) {
|
||||
<img [src]="cicConsentCapture.previewUrl" alt="Signed consent declaration" />
|
||||
} @else {
|
||||
<span class="loi-kyc-thumb-label">View file</span>
|
||||
}
|
||||
<span class="loi-kyc-card-title">{{ acc.kind }}</span>
|
||||
<span class="loi-kyc-masked">{{ acc.accountNo }} — {{ acc.productName }}</span>
|
||||
<span class="loi-kyc-masked">Balance: ₹{{ acc.accountBalance | number: '1.0-0' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@if (a.cicConsentSigned) {
|
||||
<span class="loan-badge loan-badge--verified">Signed Declaration Uploaded</span>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<!-- New Customer, or an Existing Customer with no open account on file --
|
||||
collect the charge the way a real-world merchant would: a payment link the
|
||||
customer pays externally, with the reference they hand back recorded as
|
||||
proof. -->
|
||||
@if (a.clientId && loadingShareAccounts) {
|
||||
<p class="pg-sub npa-report-note">Loading accounts…</p>
|
||||
} @else {
|
||||
<span class="loan-badge loan-badge--missing">Awaiting Signed Declaration</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="loi-cic-subsection">
|
||||
<div class="loi-cic-subsection-title">CIC Check Charge — ₹{{ cicCheckCharge }}</div>
|
||||
@if (a.clientId && realDebitableAccounts.length > 0) {
|
||||
<!-- Existing Customer with a real account -- pick which one to debit. This
|
||||
pipeline never posts an actual Fineract transaction, so the "debit" is
|
||||
recorded as a label for audit only. -->
|
||||
<p class="pg-sub npa-report-note">
|
||||
Select which account to debit the ₹{{ cicCheckCharge }} charge from:
|
||||
</p>
|
||||
<div class="loi-kyc-doc-grid">
|
||||
@for (acc of realDebitableAccounts; track acc.id) {
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-card loi-charge-account"
|
||||
[class.loi-charge-account--selected]="a.cicChargeAccountLabel.startsWith(acc.accountNo)"
|
||||
(click)="selectCicChargeAccount(a, acc)"
|
||||
>
|
||||
<span class="loi-kyc-card-title">{{ acc.kind }}</span>
|
||||
<span class="loi-kyc-masked">{{ acc.accountNo }} — {{ acc.productName }}</span>
|
||||
<span class="loi-kyc-masked">Balance: ₹{{ acc.accountBalance | number: '1.0-0' }}</span>
|
||||
@if (a.clientId) {
|
||||
<p class="pg-sub npa-report-note">
|
||||
No open savings/current account on file for this customer — collect the charge via payment link
|
||||
instead.
|
||||
</p>
|
||||
}
|
||||
@if (!a.cicPaymentLinkUrl || a.cicPaymentLinkUrl === '—') {
|
||||
<div class="loan-action-row">
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="generateCicPaymentLink(a)">
|
||||
Generate Payment Link
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<!-- New Customer, or an Existing Customer with no open account on file --
|
||||
collect the charge the way a real-world merchant would: a payment link the
|
||||
customer pays externally, with the reference they hand back recorded as
|
||||
proof. -->
|
||||
@if (a.clientId && loadingShareAccounts) {
|
||||
<p class="pg-sub npa-report-note">Loading accounts…</p>
|
||||
</div>
|
||||
} @else {
|
||||
@if (a.clientId) {
|
||||
<p class="pg-sub npa-report-note">
|
||||
No open savings/current account on file for this customer — collect the charge via payment link
|
||||
instead.
|
||||
</p>
|
||||
}
|
||||
@if (!a.cicPaymentLinkUrl || a.cicPaymentLinkUrl === '—') {
|
||||
<p class="loi-kyc-masked">{{ a.cicPaymentLinkUrl }}</p>
|
||||
<div class="loan-action-row">
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="copyCicPaymentLink(a)">
|
||||
Copy Link
|
||||
</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="emailCicPaymentLink(a)">Email</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="whatsappCicPaymentLink(a)">
|
||||
WhatsApp
|
||||
</button>
|
||||
</div>
|
||||
@if (!a.cicChargeCollected) {
|
||||
<label class="inv-form-field">
|
||||
<span>Payment Reference No. (from customer)</span>
|
||||
<input type="text" [(ngModel)]="cicPaymentRefInput" placeholder="e.g. UTR / Txn ID" />
|
||||
</label>
|
||||
<div class="loan-action-row">
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="generateCicPaymentLink(a)">
|
||||
Generate Payment Link
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="confirmCicPaymentReceived(a)">
|
||||
Confirm Payment Received
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<p class="loi-kyc-masked">{{ a.cicPaymentLinkUrl }}</p>
|
||||
<div class="loan-action-row">
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="copyCicPaymentLink(a)">
|
||||
Copy Link
|
||||
</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="emailCicPaymentLink(a)">
|
||||
Email
|
||||
</button>
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="whatsappCicPaymentLink(a)">
|
||||
WhatsApp
|
||||
</button>
|
||||
</div>
|
||||
@if (!a.cicChargeCollected) {
|
||||
<label class="inv-form-field">
|
||||
<span>Payment Reference No. (from customer)</span>
|
||||
<input type="text" [(ngModel)]="cicPaymentRefInput" placeholder="e.g. UTR / Txn ID" />
|
||||
</label>
|
||||
<div class="loan-action-row">
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="confirmCicPaymentReceived(a)">
|
||||
Confirm Payment Received
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (a.cicChargeCollected) {
|
||||
<span class="loan-badge loan-badge--verified">
|
||||
Charge Collected —
|
||||
{{ a.cicChargeMode === 'ACCOUNT_DEBIT' ? a.cicChargeAccountLabel : 'Ref ' + a.cicPaymentReference }}
|
||||
</span>
|
||||
} @else {
|
||||
<span class="loan-badge loan-badge--missing">Charge Not Collected</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
@if (a.cicChargeCollected) {
|
||||
<span class="loan-badge loan-badge--verified">
|
||||
Charge Collected —
|
||||
{{ a.cicChargeMode === 'ACCOUNT_DEBIT' ? a.cicChargeAccountLabel : 'Ref ' + a.cicPaymentReference }}
|
||||
</span>
|
||||
} @else {
|
||||
<span class="loan-badge loan-badge--missing">Charge Not Collected</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (a.cibilScore === 0) {
|
||||
<label class="inv-form-field">
|
||||
<span>Bureau</span>
|
||||
<select [(ngModel)]="a.cicBureau">
|
||||
@@ -1453,6 +1485,11 @@
|
||||
>
|
||||
📊 View Analysis
|
||||
</button>
|
||||
@if (hasStoredDoc('CIC:report')) {
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewKycSlot(a, null, 'CIC:report')">
|
||||
View file
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -1473,21 +1510,7 @@
|
||||
Push Field Inquiry to Agency
|
||||
</button>
|
||||
} @else if (a.cicReportUploaded && a.fiRequestPushed) {
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn pg-btn--primary"
|
||||
(click)="
|
||||
proceed(
|
||||
a,
|
||||
a.cicBureau +
|
||||
' report reviewed — score ' +
|
||||
a.cibilScore +
|
||||
' (' +
|
||||
cibilBandOf(a.cibilScore) +
|
||||
').'
|
||||
)
|
||||
"
|
||||
>
|
||||
<button type="button" class="pg-btn pg-btn--primary" (click)="completeCicStage(a)">
|
||||
Proceed to Field Investigation
|
||||
</button>
|
||||
}
|
||||
@@ -1536,7 +1559,12 @@
|
||||
<div class="inv-form-grid">
|
||||
<label class="inv-form-field">
|
||||
<span>Assign Field Officer</span>
|
||||
<input type="text" [(ngModel)]="fiOfficerNameInput[key]" placeholder="Officer name" />
|
||||
<select [(ngModel)]="fiOfficerNameInput[key]">
|
||||
<option value="">Select an officer</option>
|
||||
@for (staffMember of fiStaffOptions; track staffMember.id) {
|
||||
<option [value]="staffMember.displayName">{{ staffMember.displayName }}</option>
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="loan-action-row">
|
||||
@@ -1642,7 +1670,12 @@
|
||||
<div class="inv-form-grid">
|
||||
<label class="inv-form-field">
|
||||
<span>Re-Assign a New Field Officer</span>
|
||||
<input type="text" [(ngModel)]="fiOfficerNameInput[key]" placeholder="Officer name" />
|
||||
<select [(ngModel)]="fiOfficerNameInput[key]">
|
||||
<option value="">Select an officer</option>
|
||||
@for (staffMember of fiStaffOptions; track staffMember.id) {
|
||||
<option [value]="staffMember.displayName">{{ staffMember.displayName }}</option>
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div class="loan-action-row">
|
||||
@@ -1735,6 +1768,13 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (a.agentReportUploaded && hasStoredDoc('FI:agentReport')) {
|
||||
<div class="loan-action-row">
|
||||
<button type="button" class="pg-btn pg-btn--ghost" (click)="viewKycSlot(a, null, 'FI:agentReport')">
|
||||
View file
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
@if (!a.agentReportUploaded) {
|
||||
<label class="inv-form-field">
|
||||
<span>Agent Report Reference / Remark</span>
|
||||
@@ -2175,6 +2215,27 @@
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<!-- Already in the document store from an earlier session. -->
|
||||
@for (stored of storedDocsFor('LEGAL_DOC:' + doc.key); track stored.id) {
|
||||
<div class="loi-legal-doc-thumb-wrap">
|
||||
<button
|
||||
type="button"
|
||||
class="loi-kyc-thumb"
|
||||
title="{{ stored.fileName }}"
|
||||
(click)="viewStoredDoc(a, stored)"
|
||||
>
|
||||
<span class="loi-kyc-thumb-label">View file</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="loi-legal-doc-thumb-remove"
|
||||
title="Delete this stored file"
|
||||
(click)="removeStoredDoc(a, stored)"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (doc.submitted) {
|
||||
<span class="loan-badge loan-badge--verified">
|
||||
@@ -2341,10 +2402,30 @@
|
||||
</label>
|
||||
<p class="pg-sub npa-report-note">
|
||||
Disbursing also confirms EMI Activation for this account in the same step.
|
||||
@if (a.customerType === 'EXISTING' && a.clientId) {
|
||||
A real Fineract loan account will be created, approved and disbursed in Core Banking at the same time.
|
||||
} @else {
|
||||
This is a mock disbursement only — no Core Banking account exists until this customer is fully onboarded
|
||||
as a client.
|
||||
}
|
||||
</p>
|
||||
@if (a.fineractLoanError) {
|
||||
<p class="loi-kyc-error">{{ a.fineractLoanError }}</p>
|
||||
}
|
||||
<div class="loan-action-row">
|
||||
<button type="button" class="pg-btn pg-btn--success" (click)="disburseNow(a)">
|
||||
Disburse Now & Confirm EMI Activation
|
||||
<button
|
||||
type="button"
|
||||
class="pg-btn pg-btn--success"
|
||||
[disabled]="creatingRealLoan"
|
||||
(click)="disburseNow(a)"
|
||||
>
|
||||
@if (creatingRealLoan) {
|
||||
Creating Real Loan Account in Core Banking…
|
||||
} @else if (a.fineractLoanError) {
|
||||
Retry Disburse & Confirm EMI Activation
|
||||
} @else {
|
||||
Disburse Now & Confirm EMI Activation
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user