/** * 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, OnInit, OnDestroy, inject } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule } from '@angular/material/dialog'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatIconModule } from '@angular/material/icon'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { ClientsService } from 'app/clients/clients.service'; @Component({ selector: 'mifosx-din-verify-dialog', template: `

DIN Verification

@if (!verifying && !verified && !failed && !errorOccurred) {
{{ signatoryName }} CIF: {{ cifNumber }} · PAN: {{ panNumber }}
@if (entityName) {
Entity {{ entityName }}
}
Enter the Director Identification Number (DIN) issued by the Ministry of Corporate Affairs (MCA) for this authorized signatory. The system will verify the DIN and match the entity name against the director's associated companies.
DIN Number badge 8-digit DIN number from MCA
} @if (verifying) {
{{ progress }}%
@for (step of progressSteps; track step.label; let i = $index) {
{{ step.label }}
}

Verifying DIN {{ dinNumber }} for {{ signatoryName }} against MCA (Ministry of Corporate Affairs) records.

} @if (!verifying && verified) {
VERIFIED

DIN Verified Successfully

DIN {{ dinNumber }} for {{ signatoryName }} has been verified against MCA records.

DIN {{ dinNumber }}
@if (directorName) {
Director Name {{ directorName }}
}
Status {{ dinStatus || 'Active' }}
Verified On {{ verifiedDate }}
@if (dinDesignation) {
Designation {{ dinDesignation }}
}
@if (companies.length > 0) {

Associated Companies ({{ companies.length }})

@if (companyMatch) {
Entity Match Found

{{ entityName }} matches {{ matchedCompany }}

} @else {
No Entity Match

{{ entityName }} does not match any of the director's associated companies.

}
@for (company of companies; track company.companyName || $index) {
@if (company._matched) { check_circle } @else { radio_button_unchecked }
{{ company.companyName }} @if (company.cin) { CIN: {{ company.cin }} } @if (company.cin && company.designation) { · } @if (company.designation) { {{ company.designation }} } @if ((company.cin || company.designation) && company.appointmentDate) { · } @if (company.appointmentDate) { Since: {{ company.appointmentDate }} }
}
}
} @if (!verifying && failed) {
FAILED

DIN Verification Failed

DIN {{ dinNumber }} could not be verified.

@if (failReason) {
{{ failReason }}
}
} @if (!verifying && errorOccurred) {

Verification failed. Please try again.

{{ errorMessage }}

}
@if (!verifying && !verified && !failed && !errorOccurred) { } @if (!verifying && verified) { } @if (!verifying && failed) { } @if (!verifying && errorOccurred) { } `, styles: [ ` :host { display: block; min-width: 520px; } 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: 70vh; overflow: auto; } /* Signatory info */ .signatory-info { display: flex; align-items: center; gap: 12px; padding: 14px 16px; background: #f0f4f8; border-radius: 10px; margin-bottom: 10px; border: 1px solid #e2e8f0; } .sig-icon { font-size: 22px; color: #1074b9; } .sig-detail { display: flex; flex-direction: column; } .sig-name { font-size: 15px; font-weight: 700; color: #1a2332; } .sig-meta { font-size: 12px; color: #64748b; margin-top: 2px; } /* Entity info */ .entity-info { display: flex; align-items: center; gap: 10px; padding: 10px 16px; background: #fffbeb; border-radius: 8px; margin-bottom: 14px; border: 1px solid #fde68a; } .entity-icon { font-size: 18px; color: #b45309; } .entity-detail { display: flex; flex-direction: column; } .entity-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: #92400e; } .entity-name { font-size: 14px; font-weight: 700; color: #78350f; } /* DIN info bar */ .din-info-bar { display: flex; align-items: flex-start; gap: 10px; padding: 10px 14px; background: #e3f2fd; border-radius: 8px; margin-bottom: 16px; font-size: 13px; color: #333; line-height: 1.5; } .info-icon { font-size: 16px; color: #1074b9; flex-shrink: 0; margin-top: 2px; } /* DIN input */ .din-input-field { width: 100%; } .din-prefix-icon { color: #64748b; margin-right: 4px; } /* Progress */ .progress-section { display: flex; flex-direction: column; align-items: center; gap: 12px; } .progress-percent { font-size: 22px; font-weight: 700; color: #1074b9; margin-top: 4px; } .progress-steps { display: flex; flex-direction: column; gap: 10px; width: 100%; padding: 8px 16px; } .progress-step { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #9ca3af; transition: color 0.3s ease; } .progress-step.active { color: #1074b9; font-weight: 600; } .progress-step.done { color: #388e3c; } .done-icon { color: #388e3c; } .verify-msg { font-size: 14px; color: #555; line-height: 1.6; text-align: center; margin: 4px 0 0; } /* Result cards */ .result-card { display: flex; flex-direction: column; align-items: center; padding: 20px 16px; gap: 12px; } .result-icon-ring { width: 64px; height: 64px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } .success-ring { background: linear-gradient(135deg, #ecfdf5, #d1fae5); border: 3px solid #a7f3d0; } .fail-ring { background: linear-gradient(135deg, #fef2f2, #fecaca); border: 3px solid #fca5a5; } .result-shield { font-size: 28px; } .success-shield { color: #059669; } .fail-shield { color: #dc2626; } .result-badge { display: inline-block; font-size: 11px; font-weight: 800; letter-spacing: 1.5px; padding: 3px 14px; border-radius: 20px; } .success-badge { color: #059669; background: #ecfdf5; border: 1px solid #a7f3d0; } .fail-badge { color: #dc2626; background: #fef2f2; border: 1px solid #fecaca; } .result-title { font-size: 17px; font-weight: 700; color: #111827; margin: 0; } .result-detail { font-size: 13px; color: #6b7280; text-align: center; line-height: 1.6; margin: 0; max-width: 400px; } .din-highlight { color: #059669; font-family: 'JetBrains Mono', 'Fira Code', monospace; } .din-highlight-fail { color: #dc2626; font-family: 'JetBrains Mono', 'Fira Code', monospace; } .result-meta { width: 100%; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px 16px; margin-top: 4px; } .meta-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid #f3f4f6; } .meta-row:last-child { border-bottom: none; } .meta-label { font-size: 12px; color: #9ca3af; font-weight: 500; } .meta-value { font-size: 12px; color: #374151; font-weight: 600; } .meta-mono { font-family: 'JetBrains Mono', 'Fira Code', monospace; letter-spacing: 0.5px; } .meta-success { color: #059669; } /* ═══ Company Match Section ═══ */ .company-match-section { width: 100%; margin-top: 8px; } .company-match-title { display: flex; align-items: center; gap: 8px; font-size: 14px; font-weight: 700; color: #1e293b; margin: 0 0 10px; } .company-title-icon { font-size: 14px; color: #64748b; } /* Match banner */ .match-banner { display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; border-radius: 8px; margin-bottom: 10px; font-size: 13px; line-height: 1.5; } .match-banner p { margin: 2px 0 0; font-size: 12px; } .match-success { background: #ecfdf5; border: 1px solid #a7f3d0; color: #065f46; } .match-success .match-banner-icon { color: #059669; font-size: 18px; margin-top: 1px; flex-shrink: 0; } .match-warning { background: #fffbeb; border: 1px solid #fde68a; color: #92400e; } .match-warning .match-banner-icon { color: #d97706; font-size: 18px; margin-top: 1px; flex-shrink: 0; } /* Company list */ .company-list { border: 1px solid #e5e7eb; border-radius: 8px; overflow: hidden; } .company-item { display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-bottom: 1px solid #f3f4f6; transition: background 0.15s; } .company-item:last-child { border-bottom: none; } .company-item:hover { background: #f8fafc; } .company-matched { background: #f0fdf4 !important; border-left: 3px solid #22c55e; } .company-match-icon { font-size: 18px !important; width: 18px !important; height: 18px !important; flex-shrink: 0; } .company-match-icon.match { color: #22c55e; } .company-match-icon.no-match { color: #d1d5db; } .company-info { display: flex; flex-direction: column; min-width: 0; } .company-name { font-size: 13px; font-weight: 600; color: #1e293b; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .company-meta { font-size: 11px; color: #94a3b8; margin-top: 2px; } /* Fail reason */ .fail-reason { display: flex; align-items: center; gap: 10px; width: 100%; background: #fef2f2; border: 1px solid #fecaca; padding: 10px 14px; border-radius: 6px; font-size: 13px; color: #991b1b; line-height: 1.5; } .fail-reason-icon { font-size: 16px; color: #dc2626; flex-shrink: 0; } /* Error */ .error-icon-large { font-size: 48px; color: #f57c00; } .error-msg { font-size: 16px; font-weight: 600; color: #f57c00; margin: 0; text-align: center; } .error-detail { font-size: 14px; color: #555; text-align: center; } mat-dialog-actions { padding: 8px 24px 16px; } mat-dialog-actions button { display: flex; align-items: center; gap: 6px; } ` ], imports: [ CommonModule, FormsModule, MatDialogModule, MatProgressBarModule, MatButtonModule, MatFormFieldModule, MatInputModule, MatIconModule, FaIconComponent ] }) export class DinVerifyDialogComponent implements OnInit, OnDestroy { private dialogRef = inject(MatDialogRef); private data: { clientId: string; kycId: string; signatoryName: string; cifNumber: string; panNumber: string; entityName: string; } = inject(MAT_DIALOG_DATA); private clientsService = inject(ClientsService); signatoryName = ''; cifNumber = ''; panNumber = ''; entityName = ''; dinNumber = ''; verifying = false; verified = false; failed = false; errorOccurred = false; errorMessage = ''; failReason = ''; directorName = ''; dinStatus = ''; dinDesignation = ''; verifiedDate = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); /** Companies list from DIN verification response */ companies: any[] = []; /** Whether entity name matched a company */ companyMatch = false; /** The matched company name */ matchedCompany = ''; progress = 0; currentStep = 0; progressSteps = [ { label: 'Connecting to MCA portal...', icon: 'globe' as any }, { label: 'Looking up DIN records...', icon: 'search' as any }, { label: 'Matching entity with companies...', icon: 'building' as any }, { label: 'Saving verification...', icon: 'database' as any } ]; private intervalId: any; private apiResponse: any = null; private apiError: any = null; private apiDone = false; ngOnInit() { this.signatoryName = this.data?.signatoryName || ''; this.cifNumber = this.data?.cifNumber || ''; this.panNumber = this.data?.panNumber || '-'; this.entityName = this.data?.entityName || ''; } onVerify() { if (!this.dinNumber || this.dinNumber.length < 8) return; this.verifying = true; this.verified = false; this.failed = false; this.errorOccurred = false; this.apiDone = false; this.apiResponse = null; this.apiError = null; this.progress = 0; this.currentStep = 0; this.companies = []; this.companyMatch = false; this.matchedCompany = ''; this.dialogRef.disableClose = true; // Fire API call — pass clientId, kycId, dinNumber and entityName this.clientsService.verifyDIN(this.data.clientId, this.data.kycId, this.dinNumber, this.entityName).subscribe({ next: (res: any) => { this.apiResponse = res; this.apiDone = true; this.tryShowResult(); }, error: (err: any) => { this.apiError = err; this.apiDone = true; this.tryShowResult(); } }); // Animate progress this.intervalId = setInterval(() => { this.progress += 2; this.currentStep = Math.min(Math.floor(this.progress / 25), this.progressSteps.length - 1); if (this.progress >= 100) { clearInterval(this.intervalId); this.intervalId = null; this.tryShowResult(); } }, 60); } private tryShowResult() { if (!this.apiDone || this.progress < 100) return; this.verifying = false; this.dialogRef.disableClose = false; if (this.apiError) { this.errorOccurred = true; this.errorMessage = this.apiError?.error?.defaultUserMessage || this.apiError?.error?.message || this.apiError?.message || 'Unable to verify DIN. Please try again.'; return; } const res = this.apiResponse; const changes = res?.changes || res; if (changes?.dinVerified || res?.dinVerified) { this.verified = true; this.directorName = changes?.directorName || res?.directorName || ''; this.dinStatus = changes?.dinStatus || res?.dinStatus || 'Active'; this.dinDesignation = changes?.dinDesignation || res?.dinDesignation || ''; this.verifiedDate = new Date().toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); // Process company list from response const rawCompanies = changes?.companies || res?.companies || []; this.companies = rawCompanies.map((c: any) => ({ ...c, companyName: c.companyName || c.name || '', cin: c.cin || c.cinNumber || '', designation: c.designation || '', appointmentDate: c.appointmentDate || c.dateOfAppointment || '', _matched: false })); // Match entity name against company list this.matchCompanyName(); } else { this.failed = true; this.failReason = changes?.dinFailReason || res?.failReason || res?.defaultUserMessage || 'DIN could not be verified against MCA records.'; } } /** Match the entity name against the list of companies from the DIN response */ private matchCompanyName() { if (!this.entityName || this.companies.length === 0) return; const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, ''); const entityNorm = normalize(this.entityName); for (const company of this.companies) { const compNorm = normalize(company.companyName); // Exact match or substring containment if (compNorm === entityNorm || compNorm.includes(entityNorm) || entityNorm.includes(compNorm)) { company._matched = true; this.companyMatch = true; this.matchedCompany = company.companyName; break; } } } onSkip() { this.dialogRef.close({ skipped: true }); } onRetry() { this.verified = false; this.failed = false; this.errorOccurred = false; this.progress = 0; this.currentStep = 0; this.companies = []; this.companyMatch = false; this.matchedCompany = ''; } onClose(verified: boolean) { this.dialogRef.close({ verified, dinNumber: this.dinNumber, directorName: this.directorName, dinStatus: this.dinStatus, dinDesignation: this.dinDesignation, dinVerified: verified, companyMatch: this.companyMatch, matchedCompany: this.matchedCompany, companies: this.companies, response: this.apiResponse }); } ngOnDestroy() { if (this.intervalId) { clearInterval(this.intervalId); } } }