feat: add CFCFRMS module, bank menus, mock backend, and dashboard redesign
- CFCFRMS cyber fraud complaint management with dashboard, complaints, escalation, report suspect/mule/fraud txn, FMR submission, CFR lookup - Mock HTTP interceptor with 20 dummy complaints and all endpoints - Simulator service for I4C/MHA integration testing - 3-row menu bar with 22 items (7+8+8 layout) - Mega menus for General Ledger, Sub Ledger, Deposits, Share, Others, HRM, RBI with search and grouped columns - Dropdowns for Cashier, Clearing, NPA, CKYC, Advances, Letter Mgmt, Insurance, Digital - Dashboard redesign with regulatory, financial health, deposits, risk/NPA sections using static banking data - All translations added to en-US.json - FontAwesome icons registered for all new menu items Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0a32d7135e
commit
b3a4cce0cf
@@ -0,0 +1,133 @@
|
||||
<!--
|
||||
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="report-page">
|
||||
<div class="page-header">
|
||||
<h2>{{ 'labels.text.Report Mule Account' | translate }}</h2>
|
||||
<p class="subtitle">{{ 'labels.text.Flag a mule account proactively to I4C/CFCFRMS' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="report-layout">
|
||||
<mat-card class="report-card">
|
||||
<mat-card-header>
|
||||
<mat-card-title>
|
||||
<fa-icon icon="user-slash" class="mr-05"></fa-icon> {{ 'labels.text.Mule Account Details' | translate }}
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
@if (result) {
|
||||
<div class="success-result">
|
||||
<div class="success-icon"><fa-icon icon="check-circle" size="3x"></fa-icon></div>
|
||||
<h3>{{ 'labels.text.Mule Account Flagged' | translate }}</h3>
|
||||
<div class="result-details">
|
||||
<div class="result-row">
|
||||
<span class="result-label">{{ 'labels.text.Reference Number' | translate }}</span
|
||||
><span class="result-value mono">{{ result.referenceNumber }}</span>
|
||||
</div>
|
||||
<div class="result-row">
|
||||
<span class="result-label">{{ 'labels.text.Account Number' | translate }}</span
|
||||
><span class="result-value mono">{{ result.accountNumber }}</span>
|
||||
</div>
|
||||
<div class="result-row">
|
||||
<span class="result-label">{{ 'labels.text.Mule Type' | translate }}</span
|
||||
><span class="result-value">{{ result.muleType }}</span>
|
||||
</div>
|
||||
<div class="result-row">
|
||||
<span class="result-label">{{ 'labels.text.Status' | translate }}</span
|
||||
><span class="result-value status-flagged">{{ result.status }}</span>
|
||||
</div>
|
||||
<div class="result-row">
|
||||
<span class="result-label">{{ 'labels.text.Timestamp' | translate }}</span
|
||||
><span class="result-value">{{ result.reportedAt }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button mat-raised-button color="primary" (click)="resetForm()" class="mt-16">
|
||||
<fa-icon icon="plus" class="mr-05"></fa-icon> {{ 'labels.text.New Report' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ 'labels.text.Account Number' | translate }}</mat-label>
|
||||
<input matInput formControlName="accountNumber" placeholder="e.g. 50100268347291" />
|
||||
@if (form.get('accountNumber')?.hasError('required') && form.get('accountNumber')?.touched) {
|
||||
<mat-error>Account number is required</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ 'labels.text.Account Name' | translate }}</mat-label>
|
||||
<input matInput formControlName="accountName" placeholder="Account holder name" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ 'labels.text.Bank IFSC' | translate }}</mat-label>
|
||||
<input matInput formControlName="bankIfsc" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ 'labels.text.Mule Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="muleType">
|
||||
@for (t of muleTypes; track t.value) {
|
||||
<mat-option [value]="t.value">{{ t.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ 'labels.text.Transaction Pattern' | translate }}</mat-label>
|
||||
<mat-select formControlName="transactionPattern">
|
||||
@for (p of transactionPatterns; track p) {
|
||||
<mat-option [value]="p">{{ p }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ 'labels.text.Total Suspicious Amount (INR)' | translate }}</mat-label>
|
||||
<input matInput type="number" formControlName="totalSuspiciousAmount" placeholder="0.00" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ 'labels.text.Linked Fraud Ticket IDs' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
formControlName="linkedFraudTickets"
|
||||
placeholder="e.g. CFCFRMS-2024-00001, CFCFRMS-2024-00002"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="full-width">
|
||||
<mat-label>{{ 'labels.text.Remarks' | translate }}</mat-label>
|
||||
<textarea
|
||||
matInput
|
||||
formControlName="remarks"
|
||||
rows="3"
|
||||
placeholder="{{ 'labels.text.Enter remarks...' | translate }}"
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
type="submit"
|
||||
[disabled]="isSubmitting"
|
||||
class="full-width submit-btn"
|
||||
>
|
||||
@if (isSubmitting) {
|
||||
<mat-spinner diameter="20" class="inline-spinner"></mat-spinner>
|
||||
} @else {
|
||||
<fa-icon icon="paper-plane" class="mr-05"></fa-icon> {{ 'labels.text.Submit Report' | translate }}
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
.report-page {
|
||||
padding: 24px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.subtitle {
|
||||
margin: 4px 0 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.report-layout {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.report-card {
|
||||
flex: 1;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mr-05 {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.mt-16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
margin-top: 8px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.inline-spinner {
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.success-result {
|
||||
text-align: center;
|
||||
padding: 32px 16px;
|
||||
.success-icon {
|
||||
color: #10b981;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
h3 {
|
||||
color: #10b981;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.result-details {
|
||||
text-align: left;
|
||||
max-width: 500px;
|
||||
margin: 0 auto;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.result-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.result-label {
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
.result-value {
|
||||
font-weight: 600;
|
||||
}
|
||||
.mono {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
}
|
||||
.status-flagged {
|
||||
color: #ef4444;
|
||||
font-weight: 700;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
||||
import { MatDialogModule, MatDialog } from '@angular/material/dialog';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CfcfrmsService } from '../cfcfrms.service';
|
||||
import { ConfirmDialogComponent } from '../components/confirm-dialog/confirm-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'cfcfrms-report-mule',
|
||||
standalone: true,
|
||||
templateUrl: './report-mule.component.html',
|
||||
styleUrls: ['./report-mule.component.scss'],
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatButtonModule,
|
||||
MatSelectModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatSnackBarModule,
|
||||
MatDialogModule,
|
||||
FaIconComponent,
|
||||
TranslateModule
|
||||
]
|
||||
})
|
||||
export class ReportMuleComponent {
|
||||
private fb = inject(FormBuilder);
|
||||
private service = inject(CfcfrmsService);
|
||||
private snackBar = inject(MatSnackBar);
|
||||
private dialog = inject(MatDialog);
|
||||
|
||||
form: FormGroup = this.fb.group({
|
||||
accountNumber: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.minLength(8)
|
||||
]
|
||||
],
|
||||
accountName: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
bankIfsc: [
|
||||
'LINC0000001',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.pattern(/^[A-Z]{4}0[A-Z0-9]{6}$/)
|
||||
]
|
||||
],
|
||||
muleType: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
linkedFraudTickets: [''],
|
||||
transactionPattern: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
totalSuspiciousAmount: [
|
||||
null,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(1)
|
||||
]
|
||||
],
|
||||
remarks: ['']
|
||||
});
|
||||
|
||||
isSubmitting = false;
|
||||
result: any = null;
|
||||
|
||||
muleTypes = [
|
||||
{ value: 'INDIVIDUAL', label: 'Individual Account' },
|
||||
{ value: 'BUSINESS', label: 'Business / Proprietor Account' },
|
||||
{ value: 'SHELL_COMPANY', label: 'Shell Company Account' }
|
||||
];
|
||||
|
||||
transactionPatterns = [
|
||||
'Rapid inflow-outflow within hours',
|
||||
'Multiple small credits, single large withdrawal',
|
||||
'Circular fund routing across accounts',
|
||||
'High volume with no legitimate business',
|
||||
'Dormant account suddenly activated with large transactions',
|
||||
'Pattern matches known mule network'
|
||||
];
|
||||
|
||||
onSubmit(): void {
|
||||
if (this.form.invalid) {
|
||||
this.form.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
data: { title: 'Confirm Mule Report', message: 'Flag this account as a mule account and report to I4C/CFCFRMS?' }
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((confirmed) => {
|
||||
if (!confirmed) return;
|
||||
this.isSubmitting = true;
|
||||
const val = this.form.value;
|
||||
const payload = {
|
||||
...val,
|
||||
linkedFraudTickets: val.linkedFraudTickets
|
||||
? val.linkedFraudTickets
|
||||
.split(',')
|
||||
.map((s: string) => s.trim())
|
||||
.filter(Boolean)
|
||||
: []
|
||||
};
|
||||
|
||||
this.service.reportMuleAccount(payload).subscribe({
|
||||
next: (res) => {
|
||||
this.result = res;
|
||||
this.isSubmitting = false;
|
||||
this.snackBar.open('Mule account reported successfully', 'OK', { duration: 4000 });
|
||||
},
|
||||
error: () => {
|
||||
this.isSubmitting = false;
|
||||
this.snackBar.open('Failed to report mule account', 'OK', { duration: 4000 });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
resetForm(): void {
|
||||
this.form.reset({ bankIfsc: 'LINC0000001' });
|
||||
this.result = null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user