Files
cbsnexora/src/app/credit-bureau/cic-dashboard/cic-dashboard.component.html
T
niteshandClaude Opus 5.5 885b5897af feat(api-management): set up each CIC bureau as an API vendor
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>
2026-10-19 15:50:27 +05:30

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 &amp; 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>
}