feat: add Current Account module, DIP verification, charges integration, and translations

- Current Account module with full CRUD, transactions, charges, hold/release, block/unblock
- Current Products management under Products module
- DIP mobile number verification with real API call to /lookup/check
- Sanctions verification dialog
- Charges support for Current Account (chargeAppliesTo=5) in create/edit
- Fixed permissions from SAVINGSACCOUNT to CURRENTACCOUNT across all current account components
- Added all missing translation keys for Current Account in en-US.json

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Aditya
2026-03-17 18:23:37 +05:30
co-authored by Claude Opus 4.6
parent 73aa69ee02
commit 0a32d7135e
210 changed files with 15048 additions and 155 deletions
@@ -0,0 +1,271 @@
import { Component, OnInit, OnDestroy, inject } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatDialogRef, MatDialogModule, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatButtonModule } from '@angular/material/button';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { ClientsService } from 'app/clients/clients.service';
@Component({
selector: 'mifosx-sanctions-verify-dialog',
template: `
<h2 mat-dialog-title>
<fa-icon icon="shield-alt" class="dialog-title-icon"></fa-icon>
Sanctions Verification
</h2>
<mat-dialog-content>
@if (verifying) {
<mat-progress-bar mode="indeterminate" color="primary"></mat-progress-bar>
<p class="sanctions-msg">
Checking <strong>{{ clientName }}</strong> against sanctions list<br />
<strong>UN Security Council</strong> &amp;
<strong>Financial Intelligence Unit - India (FIU-IND under UNSC Resolution 2231)</strong>
</p>
}
@if (!verifying && !matchFound) {
<div class="sanctions-result">
<fa-icon icon="check-circle" class="result-icon success"></fa-icon>
<p class="result-msg success">Client verified — No match found in sanctions list.</p>
</div>
}
@if (!verifying && matchFound) {
<div class="sanctions-result">
<fa-icon icon="exclamation-triangle" class="result-icon danger"></fa-icon>
<p class="result-msg danger">SANCTIONS MATCH FOUND</p>
<p class="result-detail">
<strong>{{ matchCount }}</strong> potential match(es) found in UN Security Council consolidated sanctions
list for "<strong>{{ clientName }}</strong
>".
</p>
<div class="match-list">
@for (match of matches; track match.id) {
<div class="match-item">
<div class="match-name">{{ match.fullName }}</div>
<div class="match-meta">
<span class="match-type">{{ match.entryType }}</span>
<span class="match-ref">{{ match.referenceNumber }}</span>
@if (match.unListType) {
<span class="match-list-type">{{ match.unListType }}</span>
}
</div>
@if (match.aliases && match.aliases.length > 0) {
<div class="match-aliases"><em>Also known as:</em> {{ match.aliases.join(', ') }}</div>
}
</div>
}
</div>
<p class="block-msg">This client cannot be onboarded. Account opening is blocked due to sanctions match.</p>
</div>
}
@if (!verifying && errorOccurred) {
<div class="sanctions-result">
<fa-icon icon="exclamation-circle" class="result-icon warning"></fa-icon>
<p class="result-msg warning">Verification failed. Please try again.</p>
<p class="result-detail">{{ errorMessage }}</p>
</div>
}
</mat-dialog-content>
<mat-dialog-actions align="end">
@if (!verifying && !matchFound && !errorOccurred) {
<button mat-raised-button color="primary" (click)="close(true)">OK</button>
}
@if (!verifying && matchFound) {
<button mat-raised-button color="warn" (click)="close(false)">Close</button>
}
@if (!verifying && errorOccurred) {
<button mat-raised-button (click)="close(false)">Close</button>
}
</mat-dialog-actions>
`,
styles: [
`
:host {
display: block;
min-width: 500px;
}
h2[mat-dialog-title] {
display: flex;
align-items: center;
gap: 8px;
margin: 0;
padding: 16px 24px;
font-size: 18px;
font-weight: 600;
color: #1074b9;
}
.dialog-title-icon {
font-size: 20px;
}
mat-dialog-content {
padding: 16px 24px;
max-height: 60vh;
}
.sanctions-msg {
font-size: 14px;
color: #555;
line-height: 1.6;
margin: 16px 0 0;
text-align: center;
}
.sanctions-result {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 16px 0;
}
.result-icon {
font-size: 48px;
}
.result-icon.success {
color: #388e3c;
}
.result-icon.danger {
color: #d32f2f;
}
.result-icon.warning {
color: #f57c00;
}
.result-msg {
font-size: 16px;
font-weight: 600;
margin: 0;
text-align: center;
}
.result-msg.success {
color: #388e3c;
}
.result-msg.danger {
color: #d32f2f;
}
.result-msg.warning {
color: #f57c00;
}
.result-detail {
font-size: 14px;
color: #555;
text-align: center;
margin: 4px 0;
}
.match-list {
width: 100%;
max-height: 200px;
overflow-y: auto;
margin: 8px 0;
}
.match-item {
background: #fce4ec;
border-left: 4px solid #d32f2f;
padding: 8px 12px;
margin-bottom: 6px;
border-radius: 4px;
}
.match-name {
font-weight: 600;
font-size: 14px;
color: #333;
}
.match-meta {
display: flex;
gap: 8px;
margin-top: 4px;
flex-wrap: wrap;
}
.match-type,
.match-ref,
.match-list-type {
font-size: 11px;
padding: 2px 6px;
border-radius: 3px;
background: #ffcdd2;
color: #b71c1c;
}
.match-aliases {
font-size: 12px;
color: #666;
margin-top: 4px;
}
.block-msg {
font-size: 14px;
font-weight: 600;
color: #d32f2f;
background: #ffebee;
padding: 10px 16px;
border-radius: 4px;
text-align: center;
margin-top: 8px;
width: 100%;
}
mat-dialog-actions {
padding: 8px 24px 16px;
}
`
],
imports: [
CommonModule,
MatDialogModule,
MatProgressBarModule,
MatButtonModule,
FaIconComponent
]
})
export class SanctionsVerifyDialogComponent implements OnInit, OnDestroy {
private dialogRef = inject(MatDialogRef<SanctionsVerifyDialogComponent>);
private data: { name: string } = inject(MAT_DIALOG_DATA);
private clientsService = inject(ClientsService);
clientName = '';
verifying = true;
matchFound = false;
matchCount = 0;
matches: any[] = [];
errorOccurred = false;
errorMessage = '';
ngOnInit() {
this.clientName = this.data?.name || '';
this.dialogRef.disableClose = true;
this.clientsService.screenSanctions(this.clientName).subscribe({
next: (result: any) => {
this.verifying = false;
this.dialogRef.disableClose = false;
if (result.matchFound) {
this.matchFound = true;
this.matchCount = result.matchCount || 0;
this.matches = result.matches || [];
}
},
error: (err: any) => {
this.verifying = false;
this.dialogRef.disableClose = false;
this.errorOccurred = true;
this.errorMessage = err?.error?.message || err?.message || 'Unable to connect to sanctions screening service.';
}
});
}
ngOnDestroy() {}
close(verified: boolean) {
this.dialogRef.close(verified);
}
}