/** * 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, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatSnackBar } from '@angular/material/snack-bar'; import { AmlService } from 'app/aml/aml.service'; export interface RiskChangeDialogData { clientId: number; customerName: string; /** 1 Low, 2 Medium, 3 High; null where the customer carries no rating yet. */ currentCategory: number | null; /** What the report's own computation says the rating should be, used as the default. */ recommendedCategory: number | null; /** The one-line explanation the report already shows against the row. */ recommendationReason: string; } /** * Raises a change to a customer's KYC risk category for approval. * *

* The rating governs how often the customer is re-KYC'd and what AML thresholds they are measured * against, so it is not something one officer moves on their own. The dialog collects what it * should become and why; a checker applies it. */ @Component({ selector: 'mifosx-risk-change-dialog', standalone: true, imports: [ CommonModule, ReactiveFormsModule, MatDialogModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatSelectModule ], template: `

Change Risk Category

Customer {{ data.customerName }}
Rating on file {{ labelOf(data.currentCategory) }}

Why this came up
{{ data.recommendationReason }}

New risk category Low Medium High Pick the new category Reason Recorded against the customer's risk history A reason is required

The rating does not move now. It goes to a checker, and on approval the customer's KYC record and re-KYC due date are updated together.

{{ errorMessage }}

`, styles: [ ` .rc-summary { border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px 14px; margin-bottom: 14px; background: #f8fafc; } .rc-row { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; padding: 3px 0; } .rc-label { color: #64748b; font-size: 12.5px; } .rc-value { color: #1e293b; font-size: 13.5px; } .rc-reason { font-size: 12.5px; line-height: 1.5; color: #334155; margin: 0 0 14px; } .rc-form { display: flex; flex-direction: column; } .rc-full { width: 100%; } .rc-effect { font-size: 12.5px; line-height: 1.55; color: #1e3a8a; background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 6px; padding: 10px 12px; margin: 4px 0 0; } .rc-error { margin: 12px 0 0; padding: 10px 12px; border: 1px solid #fecaca; border-radius: 6px; background: #fef2f2; color: #b91c1c; font-size: 12.5px; } ` ] }) export class RiskChangeDialogComponent { private fb = inject(FormBuilder); private amlService = inject(AmlService); private snackBar = inject(MatSnackBar); dialogRef = inject(MatDialogRef); form: FormGroup; submitting = false; errorMessage = ''; constructor(@Inject(MAT_DIALOG_DATA) public data: RiskChangeDialogData) { this.form = this.fb.group({ // Defaulted to what the report recommends, but left editable: the reviewer has seen the // customer and the computation has not. requestedCategory: [ data.recommendedCategory ?? null, Validators.required ], reason: [ '', Validators.required ] }); } labelOf(category: number | null): string { return category === 1 ? 'Low' : category === 2 ? 'Medium' : category === 3 ? 'High' : 'Unrated'; } submit(): void { if (this.form.invalid) { this.form.markAllAsTouched(); return; } this.submitting = true; this.errorMessage = ''; const v = this.form.value; this.amlService.requestRiskChange(this.data.clientId, v.requestedCategory, (v.reason || '').trim()).subscribe({ next: () => { this.submitting = false; this.snackBar.open( `Risk change to ${this.labelOf(v.requestedCategory)} sent for approval — ${this.data.customerName}`, 'Close', { duration: 5000 } ); this.dialogRef.close(true); }, error: (err: any) => { this.submitting = false; this.errorMessage = err?.error?.defaultUserMessage || err?.error?.message || 'Could not send the request.'; } }); } }