- 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
725 lines
27 KiB
HTML
725 lines
27 KiB
HTML
<!--
|
||
Copyright since 2025 Mifos Initiative
|
||
|
||
This Source Code Form is subject to the terms of the Mozilla Public
|
||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||
-->
|
||
|
||
<div class="pg">
|
||
<header class="pg-head">
|
||
<div>
|
||
<div class="pg-eyebrow">CREDIT · LOAN ORIGINATION · {{ scopeName | uppercase }}</div>
|
||
<h1 class="pg-title">Loan Origination Pipeline</h1>
|
||
<p class="pg-sub npa-report-note">
|
||
Every sanctioning stage from application intake to disbursement — KYC/OVD, CIC credit check, Field
|
||
Investigation, Personal Discussion, Credit Committee, Risk & Legal Approval — as on {{ asOnDate }}.
|
||
</p>
|
||
</div>
|
||
<div class="pg-toolbar">
|
||
@if (isHeadOffice) {
|
||
<label class="pg-field">
|
||
<span>Branch</span>
|
||
<select class="pg-select" [ngModel]="branchFilter" (ngModelChange)="setBranch($event)">
|
||
<option value="">-- All Branches --</option>
|
||
@for (b of branches; track b.code) {
|
||
<option [value]="b.code">{{ b.name }}</option>
|
||
}
|
||
</select>
|
||
</label>
|
||
} @else {
|
||
<label class="pg-field">
|
||
<span>Branch</span>
|
||
<span class="pg-chip">{{ scopeName }}</span>
|
||
</label>
|
||
}
|
||
<button type="button" class="pg-btn pg-btn--ghost" [disabled]="generating" (click)="exportExcel()">
|
||
{{ generating ? 'Exporting…' : 'Export to Excel' }}
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="inv-kpis npa-kpis--ratio">
|
||
<div class="inv-kpi">
|
||
<span class="inv-kpi__label">Total Applications</span>
|
||
<span class="inv-kpi__value">{{ totalApplications }}</span>
|
||
<span class="inv-kpi__sub">in the active pipeline</span>
|
||
</div>
|
||
<div class="inv-kpi">
|
||
<span class="inv-kpi__label">In Progress</span>
|
||
<span class="inv-kpi__value">{{ inProgressCount }}</span>
|
||
<span class="inv-kpi__sub">moving through the workflow</span>
|
||
</div>
|
||
<div class="inv-kpi">
|
||
<span class="inv-kpi__label">On Hold</span>
|
||
<span class="inv-kpi__value npa-warn">{{ onHoldCount }}</span>
|
||
<span class="inv-kpi__sub">discrepancy under resolution</span>
|
||
</div>
|
||
<div class="inv-kpi">
|
||
<span class="inv-kpi__label">Rejected</span>
|
||
<span class="inv-kpi__value npa-warn">{{ rejectedCount }}</span>
|
||
<span class="inv-kpi__sub">declined at Committee/Risk</span>
|
||
</div>
|
||
<div class="inv-kpi">
|
||
<span class="inv-kpi__label">Disbursed</span>
|
||
<span class="inv-kpi__value">{{ disbursedApplications.length }}</span>
|
||
<span class="inv-kpi__sub">₹{{ disbursedAmount | number: '1.0-0' }} total</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="cctr-tabs">
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[class.pg-btn--active]="activeView === 'pipeline'"
|
||
(click)="setView('pipeline')"
|
||
>
|
||
Pipeline
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[class.pg-btn--active]="activeView === 'new'"
|
||
(click)="setView('new')"
|
||
>
|
||
New Application
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[class.pg-btn--active]="activeView === 'channel'"
|
||
(click)="setView('channel')"
|
||
>
|
||
Channel-wise Report
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[class.pg-btn--active]="activeView === 'bc'"
|
||
(click)="setView('bc')"
|
||
>
|
||
BC-wise Report
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[class.pg-btn--active]="activeView === 'share'"
|
||
(click)="setView('share')"
|
||
>
|
||
Share Member Report
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[class.pg-btn--active]="activeView === 'target'"
|
||
(click)="setView('target')"
|
||
>
|
||
Target vs Actual
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[class.pg-btn--active]="activeView === 'rates'"
|
||
(click)="setView('rates')"
|
||
>
|
||
Interest & Share Rate Chart
|
||
</button>
|
||
</div>
|
||
|
||
<!-- ===================== PIPELINE ===================== -->
|
||
@if (activeView === 'pipeline') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Filters</div>
|
||
<div class="inv-form-grid">
|
||
<label class="inv-form-field">
|
||
<span>Channel</span>
|
||
<select [(ngModel)]="channelFilter" (ngModelChange)="currentPage = 1">
|
||
<option value="">-- All --</option>
|
||
<option value="BRANCH">Branch</option>
|
||
<option value="BC">Business Correspondent (BC)</option>
|
||
<option value="SELF_APPLY">Self-Apply (Digital)</option>
|
||
</select>
|
||
</label>
|
||
<label class="inv-form-field">
|
||
<span>Stage</span>
|
||
<select [(ngModel)]="stageFilter" (ngModelChange)="currentPage = 1">
|
||
<option value="">-- All --</option>
|
||
@for (s of stageOrder; track s) {
|
||
<option [value]="s">{{ stageLabels[s] }}</option>
|
||
}
|
||
</select>
|
||
</label>
|
||
<label class="inv-form-field">
|
||
<span>Status</span>
|
||
<select [(ngModel)]="statusFilter" (ngModelChange)="currentPage = 1">
|
||
<option value="">-- All --</option>
|
||
<option value="IN_PROGRESS">In Progress</option>
|
||
<option value="ON_HOLD">On Hold</option>
|
||
<option value="REJECTED">Rejected</option>
|
||
<option value="DISBURSED">Disbursed</option>
|
||
</select>
|
||
</label>
|
||
<label class="inv-form-field">
|
||
<span>Search (Application No / Applicant)</span>
|
||
<input
|
||
type="text"
|
||
[(ngModel)]="searchText"
|
||
(ngModelChange)="currentPage = 1"
|
||
placeholder="e.g. Ramesh, LN0102..."
|
||
/>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Applications ({{ filteredApplications.length }})</div>
|
||
<div class="pg-table-scroll">
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Application No</th>
|
||
<th>Applicant</th>
|
||
<th>Branch</th>
|
||
<th>Channel</th>
|
||
<th>Product</th>
|
||
<th class="num">Requested Amount</th>
|
||
<th>Current Stage</th>
|
||
<th class="num">Progress</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (a of paged(filteredApplications); track a.applicationNo) {
|
||
<tr class="cctr-branch-row" (click)="openApplication(a)">
|
||
<td>
|
||
<span class="pg-chip">{{ a.applicationNo }}</span>
|
||
</td>
|
||
<td>{{ a.applicantName }}</td>
|
||
<td>{{ a.branchName }}</td>
|
||
<td>{{ channelLabels[a.channel] }}</td>
|
||
<td>{{ a.productName }}</td>
|
||
<td class="num">₹{{ a.requestedAmount | number: '1.0-0' }}</td>
|
||
<td>{{ stageLabel(a.currentStage) }}</td>
|
||
<td class="num">{{ progress(a) }}%</td>
|
||
<td>
|
||
<span [class]="badgeClass(a)">{{ statusLabel(a.overallStatus) }}</span>
|
||
</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="9" class="pg-empty">No applications match these filters.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (filteredApplications.length > pageSize) {
|
||
<div class="pg-pagination">
|
||
<span class="pg-pagination__info"
|
||
>Showing {{ rangeStart(filteredApplications) }}–{{ rangeEnd(filteredApplications) }} of
|
||
{{ filteredApplications.length }}</span
|
||
>
|
||
<div class="pg-pagination__controls">
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === 1"
|
||
(click)="goToPage(currentPage - 1, filteredApplications)"
|
||
>
|
||
‹ Prev
|
||
</button>
|
||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(filteredApplications) }}</span>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === pageCount(filteredApplications)"
|
||
(click)="goToPage(currentPage + 1, filteredApplications)"
|
||
>
|
||
Next ›
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== NEW APPLICATION ===================== -->
|
||
@if (activeView === 'new') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">New Application — Channel Intake</div>
|
||
<div class="inv-form-grid">
|
||
<label class="inv-form-field">
|
||
<span>* Channel</span>
|
||
<select [(ngModel)]="newApp.channel">
|
||
<option value="BRANCH">Branch</option>
|
||
<option value="BC">Business Correspondent (BC)</option>
|
||
<option value="SELF_APPLY">Self-Apply (Digital)</option>
|
||
</select>
|
||
</label>
|
||
@if (newApp.channel === 'BC') {
|
||
<label class="inv-form-field">
|
||
<span>* BC Agent / Point</span>
|
||
<input type="text" [(ngModel)]="newApp.bcAgentName" placeholder="e.g. Suvidha BC Point — Andheri" />
|
||
</label>
|
||
}
|
||
<label class="inv-form-field">
|
||
<span>* Customer Type</span>
|
||
<select [(ngModel)]="newApp.customerType" (ngModelChange)="onCustomerTypeChange()">
|
||
<option value="NEW">New Customer</option>
|
||
<option value="EXISTING">Existing Customer</option>
|
||
</select>
|
||
</label>
|
||
@if (newApp.customerType === 'EXISTING') {
|
||
@if (!newApp.clientId) {
|
||
<label class="inv-form-field">
|
||
<span>* Search Existing Client</span>
|
||
<input
|
||
type="text"
|
||
[(ngModel)]="existingClientSearchText"
|
||
(ngModelChange)="onExistingClientSearch($event)"
|
||
placeholder="Name, account no., mobile..."
|
||
/>
|
||
</label>
|
||
} @else {
|
||
<label class="inv-form-field">
|
||
<span>* Applicant</span>
|
||
<div class="loi-selected-client">
|
||
<span>{{ newApp.applicantName }}</span>
|
||
<button type="button" class="pg-btn" (click)="clearExistingClient()">Change</button>
|
||
</div>
|
||
</label>
|
||
}
|
||
} @else {
|
||
<label class="inv-form-field">
|
||
<span>* Applicant Name</span>
|
||
<input type="text" [(ngModel)]="newApp.applicantName" placeholder="Full name" />
|
||
</label>
|
||
}
|
||
<label class="inv-form-field">
|
||
<span>* Branch</span>
|
||
<select [(ngModel)]="newApp.branchCode" [disabled]="!isHeadOffice">
|
||
@for (b of newApplicationBranches; track b.code) {
|
||
<option [value]="b.code">{{ b.name }}</option>
|
||
}
|
||
</select>
|
||
</label>
|
||
<label class="inv-form-field">
|
||
<span>* Product</span>
|
||
<select [(ngModel)]="newApp.productCode" (ngModelChange)="onProductChange()">
|
||
@for (p of products; track p.code) {
|
||
<option [value]="p.code">{{ p.name }}</option>
|
||
}
|
||
</select>
|
||
</label>
|
||
<label class="inv-form-field">
|
||
<span>* Product Type</span>
|
||
<select [(ngModel)]="newApp.productType">
|
||
@for (t of newAppProductTypeOptions; track t) {
|
||
<option [value]="t">{{ productTypeLabels[t] }}</option>
|
||
}
|
||
</select>
|
||
</label>
|
||
<label class="inv-form-field">
|
||
<span>* Requested Amount</span>
|
||
<input type="number" [(ngModel)]="newApp.requestedAmount" placeholder="₹" />
|
||
</label>
|
||
<label class="inv-form-field">
|
||
<span>Tenure (Months)</span>
|
||
<input type="number" [(ngModel)]="newApp.tenureMonths" placeholder="e.g. 36" />
|
||
</label>
|
||
</div>
|
||
|
||
@if (newApp.customerType === 'EXISTING' && !newApp.clientId) {
|
||
@if (searchingExistingClient) {
|
||
<p class="loi-search-hint">Searching…</p>
|
||
} @else if (existingClientSearchText.length >= 2 && existingClientSearchResults.length === 0) {
|
||
<p class="loi-search-hint">No clients found.</p>
|
||
}
|
||
@if (existingClientSearchResults.length > 0) {
|
||
<div class="loi-search-results">
|
||
@for (client of existingClientSearchResults; track client.id) {
|
||
<button type="button" class="loi-search-row" (click)="selectExistingClient(client)">
|
||
<span class="loi-search-name">{{ client.displayName }}</span>
|
||
<span class="loi-search-meta">{{ client.accountNo ?? client.officeName ?? '' }}</span>
|
||
</button>
|
||
}
|
||
</div>
|
||
}
|
||
}
|
||
|
||
<div class="inv-form-actions">
|
||
<button type="button" class="pg-btn pg-btn--primary" (click)="saveNewApplication()">Submit Application</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== CHANNEL-WISE REPORT ===================== -->
|
||
@if (activeView === 'channel') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Channel-wise Report — Branch / BC / Self-Apply</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Channel</th>
|
||
<th class="num">Applications</th>
|
||
<th class="num">Requested Amount</th>
|
||
<th class="num col-in">Disbursed Count</th>
|
||
<th class="num col-in">Disbursed Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of channelReport; track r.channel) {
|
||
<tr>
|
||
<td>{{ r.label }}</td>
|
||
<td class="num">{{ r.count }}</td>
|
||
<td class="num">₹{{ r.requestedAmount | number: '1.0-0' }}</td>
|
||
<td class="num col-in">{{ r.disbursedCount }}</td>
|
||
<td class="num col-in">₹{{ r.disbursedAmount | number: '1.0-0' }}</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
<tfoot>
|
||
<tr class="cctr-total-row">
|
||
<td>Total</td>
|
||
<td class="num">{{ totalApplications }}</td>
|
||
<td class="num">
|
||
₹{{
|
||
channelReport[0].requestedAmount + channelReport[1].requestedAmount + channelReport[2].requestedAmount
|
||
| number: '1.0-0'
|
||
}}
|
||
</td>
|
||
<td class="num">{{ disbursedApplications.length }}</td>
|
||
<td class="num">₹{{ disbursedAmount | number: '1.0-0' }}</td>
|
||
</tr>
|
||
</tfoot>
|
||
</table>
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== BC-WISE REPORT ===================== -->
|
||
@if (activeView === 'bc') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">
|
||
BC-wise Report — Business Correspondent Sourced Applications ({{ bcApplications.length }})
|
||
</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>BC Agent / Point</th>
|
||
<th class="num">Applications</th>
|
||
<th class="num">Requested Amount</th>
|
||
<th class="num col-in">Disbursed Count</th>
|
||
<th class="num col-in">Disbursed Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (r of bcReport; track r.bcAgentName) {
|
||
<tr>
|
||
<td>{{ r.bcAgentName }}</td>
|
||
<td class="num">{{ r.count }}</td>
|
||
<td class="num">₹{{ r.requestedAmount | number: '1.0-0' }}</td>
|
||
<td class="num col-in">{{ r.disbursedCount }}</td>
|
||
<td class="num col-in">₹{{ r.disbursedAmount | number: '1.0-0' }}</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="5" class="pg-empty">No BC-sourced applications in this branch.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== SHARE MEMBER REPORT ===================== -->
|
||
@if (activeView === 'share') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Share Member Report</div>
|
||
<p class="pg-sub npa-report-note">
|
||
A cooperative-bank borrower must hold membership shares in the society before a loan can be sanctioned.
|
||
{{ shareMemberCount }} of {{ totalApplications }} applicants are already members; the rest need membership
|
||
allotted before Credit Committee approval — done from the application's own detail screen.
|
||
</p>
|
||
<div class="pg-table-scroll">
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Application No</th>
|
||
<th>Applicant</th>
|
||
<th>Branch</th>
|
||
<th>Current Stage</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (a of paged(nonMemberApplications); track a.applicationNo) {
|
||
<tr class="cctr-branch-row" (click)="openApplication(a)">
|
||
<td>
|
||
<span class="pg-chip">{{ a.applicationNo }}</span>
|
||
</td>
|
||
<td>{{ a.applicantName }}</td>
|
||
<td>{{ a.branchName }}</td>
|
||
<td>{{ stageLabel(a.currentStage) }}</td>
|
||
<td>
|
||
<span [class]="badgeClass(a)">{{ statusLabel(a.overallStatus) }}</span>
|
||
</td>
|
||
</tr>
|
||
} @empty {
|
||
<tr>
|
||
<td colspan="5" class="pg-empty">Every active applicant in this branch already holds membership.</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
@if (nonMemberApplications.length > pageSize) {
|
||
<div class="pg-pagination">
|
||
<span class="pg-pagination__info"
|
||
>Showing {{ rangeStart(nonMemberApplications) }}–{{ rangeEnd(nonMemberApplications) }} of
|
||
{{ nonMemberApplications.length }}</span
|
||
>
|
||
<div class="pg-pagination__controls">
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === 1"
|
||
(click)="goToPage(currentPage - 1, nonMemberApplications)"
|
||
>
|
||
‹ Prev
|
||
</button>
|
||
<span class="pg-pagination__page">Page {{ currentPage }} of {{ pageCount(nonMemberApplications) }}</span>
|
||
<button
|
||
type="button"
|
||
class="pg-btn pg-btn--ghost"
|
||
[disabled]="currentPage === pageCount(nonMemberApplications)"
|
||
(click)="goToPage(currentPage + 1, nonMemberApplications)"
|
||
>
|
||
Next ›
|
||
</button>
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== TARGET VS ACTUAL ===================== -->
|
||
@if (activeView === 'target') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Branch-wise Disbursement Target — Annual</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Branch</th>
|
||
<th class="num">Target</th>
|
||
<th class="num col-in">Disbursed (Actual)</th>
|
||
<th class="num">Achievement %</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (row of branchTargetRows; track row.branchCode) {
|
||
<tr>
|
||
<td>{{ row.branchName }}</td>
|
||
<td class="num">₹{{ row.target | number: '1.0-0' }}</td>
|
||
<td class="num col-in">₹{{ row.actual | number: '1.0-0' }}</td>
|
||
<td class="num">{{ row.achievementPct | number: '1.1-1' }}%</td>
|
||
<td>
|
||
<span
|
||
class="loan-badge"
|
||
[class.loan-badge--disbursed]="row.status !== 'BEHIND'"
|
||
[class.loan-badge--hold]="row.status === 'BEHIND'"
|
||
>
|
||
{{ statusLabelOf(row.status) }}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Product Mix — Target % vs Actual % (of Disbursed Amount)</div>
|
||
<p class="pg-sub npa-report-note">
|
||
KCC and Cash Credit (MSME) targets are RBI's actual Priority Sector Lending sub-targets — Agriculture 18% and
|
||
Micro Enterprises 7.5% of Adjusted Net Bank Credit (shown here against this module's own mix, since RBI sets
|
||
them against the bank's whole loan book, not one product line). The rest are internal business-plan targets, not
|
||
RBI-mandated.
|
||
</p>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Product</th>
|
||
<th>Target Basis</th>
|
||
<th class="num">Target %</th>
|
||
<th class="num col-in">Actual %</th>
|
||
<th class="num">Variance</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (row of productTargetRows; track row.code) {
|
||
<tr>
|
||
<td>{{ row.label }}</td>
|
||
<td>
|
||
<span class="loan-badge" [class.loan-badge--progress]="row.isRbiMandated">
|
||
{{ row.isRbiMandated ? 'RBI PSL Sub-Target' : 'Internal Target' }}
|
||
</span>
|
||
</td>
|
||
<td class="num">{{ row.targetPct | number: '1.0-1' }}%</td>
|
||
<td class="num col-in">{{ row.actualPct | number: '1.1-1' }}%</td>
|
||
<td class="num" [class.col-out]="row.variancePct < 0" [class.col-in]="row.variancePct > 0">
|
||
{{ row.variancePct > 0 ? '+' : '' }}{{ row.variancePct | number: '1.1-1' }} pp
|
||
</td>
|
||
<td>
|
||
<span
|
||
class="loan-badge"
|
||
[class.loan-badge--disbursed]="row.status !== 'BEHIND'"
|
||
[class.loan-badge--hold]="row.status === 'BEHIND'"
|
||
>
|
||
{{ statusLabelOf(row.status) }}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Channel Mix — Target % vs Actual % (of Disbursed Amount)</div>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Channel</th>
|
||
<th class="num">Target %</th>
|
||
<th class="num col-in">Actual %</th>
|
||
<th class="num">Variance</th>
|
||
<th>Status</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (row of channelTargetRows; track row.code) {
|
||
<tr>
|
||
<td>{{ row.label }}</td>
|
||
<td class="num">{{ row.targetPct | number: '1.0-1' }}%</td>
|
||
<td class="num col-in">{{ row.actualPct | number: '1.1-1' }}%</td>
|
||
<td class="num" [class.col-out]="row.variancePct < 0" [class.col-in]="row.variancePct > 0">
|
||
{{ row.variancePct > 0 ? '+' : '' }}{{ row.variancePct | number: '1.1-1' }} pp
|
||
</td>
|
||
<td>
|
||
<span
|
||
class="loan-badge"
|
||
[class.loan-badge--disbursed]="row.status !== 'BEHIND'"
|
||
[class.loan-badge--hold]="row.status === 'BEHIND'"
|
||
>
|
||
{{ statusLabelOf(row.status) }}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
}
|
||
|
||
<!-- ===================== INTEREST & SHARE RATE CHART ===================== -->
|
||
@if (activeView === 'rates') {
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Product-wise — Interest Rate & Share Capital Required</div>
|
||
<p class="pg-sub npa-report-note">
|
||
Total Interest is an illustrative simple-interest figure (Amount x Rate x Tenure in years) at the product's own
|
||
min/max amount and tenure band — actual repayment is on a reducing balance and depends on the amount and tenure
|
||
chosen at sanction. Share Capital Required is the minimum a borrower must hold per bank bye-laws, proportional
|
||
to the loan amount.
|
||
</p>
|
||
<div class="pg-table-scroll">
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Product</th>
|
||
<th>Type</th>
|
||
<th class="num">Amount Range</th>
|
||
<th class="num">Interest Rate (p.a.)</th>
|
||
<th class="num">Tenure Range</th>
|
||
<th class="num">Illustrative Total Interest</th>
|
||
<th class="num">Share Capital Required</th>
|
||
<th class="num">Illustrative Share Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (p of products; track p.code) {
|
||
<tr>
|
||
<td>{{ p.name }}</td>
|
||
<td>
|
||
<span
|
||
class="loan-badge"
|
||
[class.loan-badge--progress]="p.secured"
|
||
[class.loan-badge--hold]="!p.secured"
|
||
>
|
||
{{ p.secured ? 'Secured' : 'Unsecured' }}
|
||
</span>
|
||
</td>
|
||
<td class="num">₹{{ p.minAmt | number: '1.0-0' }} – ₹{{ p.maxAmt | number: '1.0-0' }}</td>
|
||
<td class="num">{{ p.minRate | number: '1.1-1' }}% – {{ p.maxRate | number: '1.1-1' }}%</td>
|
||
<td class="num">{{ p.minTenure }} – {{ p.maxTenure }} months</td>
|
||
<td class="num">
|
||
₹{{ illustrativeInterest(p.minAmt, p.minRate, p.minTenure) | number: '1.0-0' }} – ₹{{
|
||
illustrativeInterest(p.maxAmt, p.maxRate, p.maxTenure) | number: '1.0-0'
|
||
}}
|
||
</td>
|
||
<td class="num">{{ p.shareLinkagePercent | number: '1.1-1' }}%</td>
|
||
<td class="num">
|
||
₹{{ p.minAmt * (p.shareLinkagePercent / 100) | number: '1.0-0' }} – ₹{{
|
||
p.maxAmt * (p.shareLinkagePercent / 100) | number: '1.0-0'
|
||
}}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="pg-card">
|
||
<div class="pos-section-label">Amount Slab-wise — Share Capital Required (All Products)</div>
|
||
<p class="pg-sub npa-report-note">
|
||
A general slab schedule by loan quantum, independent of product — where this differs from the product-wise
|
||
minimum above, whichever is higher governs the actual requirement.
|
||
</p>
|
||
<table class="pg-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="num">Loan Amount Slab</th>
|
||
<th class="num">Share Capital Required</th>
|
||
<th class="num">Illustrative Share Amount</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@for (row of shareLinkageSlabRows; track row.minAmt) {
|
||
<tr>
|
||
<td class="num">
|
||
@if (row.maxAmt !== null) {
|
||
₹{{ row.minAmt | number: '1.0-0' }} – ₹{{ row.maxAmt | number: '1.0-0' }}
|
||
} @else {
|
||
Above ₹{{ row.minAmt | number: '1.0-0' }}
|
||
}
|
||
</td>
|
||
<td class="num">{{ row.sharePercent | number: '1.1-1' }}%</td>
|
||
<td class="num">
|
||
@if (row.shareAtMax !== null) {
|
||
₹{{ row.shareAtMin | number: '1.0-0' }} – ₹{{ row.shareAtMax | number: '1.0-0' }}
|
||
} @else {
|
||
From ₹{{ row.shareAtMin | number: '1.0-0' }}
|
||
}
|
||
</td>
|
||
</tr>
|
||
}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<div class="inv-toast" [class.show]="toastVisible">{{ toastMsg }}</div>
|