CIBIL, Experian, Equifax and CRIF are vendors under API Management (active = whitelisted) and the Credit Bureau dashboard reads them from there; the separate CIC whitelist and API mapping screens are removed. Mapping tables show the value each row finds in the sample payload, and bureau cards link to their set-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
263 lines
9.6 KiB
HTML
263 lines
9.6 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="cic-page">
|
|
<div class="cic-head">
|
|
<div>
|
|
<h1>Credit Bureau — Customer Enquiry Alerts</h1>
|
|
<p>
|
|
Our customers who applied for a loan at another bank or NBFC, as reported by the CIC (CIBIL, Experian, Equifax,
|
|
CRIF): what loan and how much. The CIC does not tell which bank or lender it was.
|
|
</p>
|
|
</div>
|
|
<div class="cic-actions">
|
|
<select [(ngModel)]="days" (ngModelChange)="load()" class="cic-btn" style="padding-right: 8px">
|
|
<option [ngValue]="7">Last 7 days</option>
|
|
<option [ngValue]="30">Last 30 days</option>
|
|
<option [ngValue]="90">Last 90 days</option>
|
|
<option [ngValue]="180">Last 180 days</option>
|
|
</select>
|
|
<a class="cic-btn" routerLink="/credit-bureau/fetch-log">Fetch log</a>
|
|
@if (whitelisted.length) {
|
|
<button
|
|
class="cic-btn primary"
|
|
(click)="fetchNow('ALL')"
|
|
[disabled]="fetching !== null"
|
|
*mifosxHasPermission="'FETCH_CICALERT'"
|
|
>
|
|
{{ fetching === 'ALL' ? 'Fetching…' : 'Fetch alerts now' }}
|
|
</button>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
@if (error) {
|
|
<div class="cic-alert error">{{ error }}</div>
|
|
}
|
|
@if (info) {
|
|
<div class="cic-alert success">{{ info }}</div>
|
|
}
|
|
@if (data && !whitelisted.length) {
|
|
<div class="cic-alert warn">
|
|
No CIC is whitelisted yet, so no alerts are being received. Switch a bureau on (CIBIL, Experian, Equifax or CRIF)
|
|
under AI / Intelligence Modules → API Management → API Vendors & Setup.
|
|
</div>
|
|
}
|
|
@if (loading && !data) {
|
|
<div class="cic-card cic-empty">Loading…</div>
|
|
}
|
|
|
|
@if (data) {
|
|
<div class="cic-grid cols-4" style="margin-bottom: 16px">
|
|
<div class="cic-stat">
|
|
<div class="k">New alerts</div>
|
|
<div class="v" [class.tone-bad]="data.newAlerts > 0">{{ data.newAlerts }}</div>
|
|
<div class="s">not yet looked at</div>
|
|
</div>
|
|
<div class="cic-stat">
|
|
<div class="k">Customers enquired elsewhere</div>
|
|
<div class="v">{{ data.customersInPeriod }}</div>
|
|
<div class="s">{{ data.alertsInPeriod }} enquiries · last {{ days }} days</div>
|
|
</div>
|
|
<div class="cic-stat">
|
|
<div class="k">Amount applied elsewhere</div>
|
|
<div class="v">₹{{ data.amountInPeriod | number: '1.0-0' }}</div>
|
|
<div class="s">in total · last {{ days }} days</div>
|
|
</div>
|
|
<div class="cic-stat">
|
|
<div class="k">Unmatched alerts</div>
|
|
<div class="v" [class.tone-warn]="data.unmatchedOpen > 0">{{ data.unmatchedOpen }}</div>
|
|
<div class="s">bureau sent, customer not found · {{ data.receivedToday }} received today</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent -->
|
|
<div class="cic-card">
|
|
<h2>Latest enquiries on our customers</h2>
|
|
@if (data.recent?.length) {
|
|
<div class="cic-table-wrap">
|
|
<table class="cic-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Enquiry date</th>
|
|
<th>Our customer</th>
|
|
<th>Branch</th>
|
|
<th>Loan type</th>
|
|
<th class="num">Amount (₹)</th>
|
|
<th>Purpose</th>
|
|
<th>Bureau</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (a of data.recent; track a.id) {
|
|
<tr class="clickable" (click)="openAlertId = a.id">
|
|
<td>{{ a.enquiryDate | date: 'dd MMM yyyy' }}</td>
|
|
<td>
|
|
<b>{{ a.clientName }}</b>
|
|
<div class="muted" style="font-size: 11px">{{ a.clientAccountNo }}</div>
|
|
</td>
|
|
<td>{{ a.officeName }}</td>
|
|
<td>{{ a.loanType }}</td>
|
|
<td class="num">{{ a.amount | number: '1.0-0' }}</td>
|
|
<td>{{ a.purpose }}</td>
|
|
<td>{{ a.bureauCode }}</td>
|
|
<td>
|
|
<span class="cic-badge" [class]="'cic-badge ' + statusTone(a.status)">{{ a.status }}</span>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
} @else {
|
|
<div class="cic-empty">
|
|
No enquiry alerts yet. They appear here as soon as a CIC reports one of our customers.
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="cic-split">
|
|
<!-- Repeat customers -->
|
|
<div class="cic-card">
|
|
<h2>
|
|
Customers applying elsewhere repeatedly
|
|
<span class="cic-card-sub">2+ enquiries · last {{ days }} days</span>
|
|
</h2>
|
|
@if (data.repeatCustomers?.length) {
|
|
<div class="cic-table-wrap">
|
|
<table class="cic-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Customer</th>
|
|
<th class="num">Enquiries</th>
|
|
<th class="num">Total applied (₹)</th>
|
|
<th>Last</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@for (c of data.repeatCustomers; track c.clientId) {
|
|
<tr>
|
|
<td>
|
|
<b>{{ c.clientName }}</b>
|
|
<div class="muted" style="font-size: 11px">{{ c.accountNo }}</div>
|
|
</td>
|
|
<td class="num tone-bad">
|
|
<b>{{ c.alerts }}</b>
|
|
</td>
|
|
<td class="num">{{ c.amount | number: '1.0-0' }}</td>
|
|
<td>{{ c.lastEnquiryDate | date: 'dd MMM' }}</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
} @else {
|
|
<div class="cic-empty">No customer has more than one enquiry in this period.</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Trend -->
|
|
<div class="cic-card">
|
|
<h2>Enquiries per day <span class="cic-card-sub">last 14 days</span></h2>
|
|
<div class="cic-trend">
|
|
@for (p of data.trend14Days; track p.date) {
|
|
<div
|
|
class="col"
|
|
[title]="(p.date | date: 'dd MMM') + ': ' + p.count + ' enquiries, ₹' + (p.amount | number: '1.0-0')"
|
|
>
|
|
<span class="n">{{ p.count || '' }}</span>
|
|
<div class="bar" [style.height.%]="pct(p.count, data.trend14Days, 'count')"></div>
|
|
<span class="d">{{ p.date | date: 'dd' }}</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="cic-card" style="margin-bottom: 0">
|
|
<h2>By loan type</h2>
|
|
@for (g of data.byLoanType; track g.label) {
|
|
<div style="margin-bottom: 10px">
|
|
<div style="display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 4px">
|
|
<span>{{ g.label }}</span>
|
|
<span class="muted">{{ g.count }} · ₹{{ g.amount | number: '1.0-0' }}</span>
|
|
</div>
|
|
<div class="cic-bar"><div [style.width.%]="pct(g.count, data.byLoanType, 'count')"></div></div>
|
|
</div>
|
|
} @empty {
|
|
<div class="cic-empty">No data.</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bureau fetch status -->
|
|
<div class="cic-card" style="margin-top: 16px">
|
|
<h2>
|
|
Alert feed from each CIC
|
|
<a class="cic-btn sm" routerLink="/credit-bureau/fetch-log">Fetch log</a>
|
|
</h2>
|
|
<div class="cic-grid cols-4">
|
|
@for (b of data.bureaus; track b.id) {
|
|
<div class="cic-stat">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; gap: 6px">
|
|
<b>{{ b.code }}</b>
|
|
<span class="cic-badge" [class.good]="b.whitelisted">{{ b.whitelisted ? 'Whitelisted' : 'Off' }}</span>
|
|
</div>
|
|
<div class="s" style="margin-top: 6px">
|
|
{{ b.environment }} · {{ b.autoFetch ? 'auto every ' + b.intervalMin + ' min' : 'manual fetch only' }}
|
|
</div>
|
|
<div class="s">
|
|
Last fetch:
|
|
@if (b.lastFetchOn) {
|
|
<span class="cic-badge" [class]="'cic-badge ' + fetchTone(b.lastFetchStatus)">{{
|
|
b.lastFetchStatus
|
|
}}</span>
|
|
{{ b.lastFetchOn | slice: 0 : 16 }}
|
|
} @else {
|
|
never
|
|
}
|
|
</div>
|
|
@if (b.lastFetchMessage) {
|
|
<div class="s" style="font-size: 11px">{{ b.lastFetchMessage }}</div>
|
|
}
|
|
<div class="s" style="font-size: 11px; margin-top: 4px">
|
|
@if (b.environment === 'SIMULATOR') {
|
|
Simulator: no call goes to {{ b.code }}; the alerts are made up by the CBS for testing.
|
|
} @else {
|
|
Calls {{ b.code }} {{ b.environment }} with the API set up under API Management.
|
|
}
|
|
</div>
|
|
@if (b.whitelisted) {
|
|
<button
|
|
class="cic-btn sm"
|
|
style="margin-top: 8px"
|
|
(click)="fetchNow(b.id)"
|
|
[disabled]="fetching !== null"
|
|
*mifosxHasPermission="'FETCH_CICALERT'"
|
|
>
|
|
{{ fetching === b.id ? 'Fetching…' : 'Fetch now' }}
|
|
</button>
|
|
}
|
|
<a class="cic-btn sm" style="margin-top: 8px; margin-left: 4px" [routerLink]="setupRoute(b.code)">Set up</a>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@if (openAlertId) {
|
|
<mifosx-cic-alert-detail
|
|
[alertId]="openAlertId"
|
|
(closed)="openAlertId = null"
|
|
(changed)="onChanged()"
|
|
></mifosx-cic-alert-detail>
|
|
}
|