Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
923 lines
26 KiB
TypeScript
923 lines
26 KiB
TypeScript
/**
|
|
* 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: `
|
|
<h2 mat-dialog-title>
|
|
<fa-icon icon="id-badge" class="dialog-title-icon"></fa-icon>
|
|
DIN Verification
|
|
</h2>
|
|
<mat-dialog-content>
|
|
<!-- Step 1: Enter DIN -->
|
|
@if (!verifying && !verified && !failed && !errorOccurred) {
|
|
<div class="din-entry">
|
|
<div class="signatory-info">
|
|
<fa-icon icon="user-shield" class="sig-icon"></fa-icon>
|
|
<div class="sig-detail">
|
|
<span class="sig-name">{{ signatoryName }}</span>
|
|
<span class="sig-meta">CIF: {{ cifNumber }} · PAN: {{ panNumber }}</span>
|
|
</div>
|
|
</div>
|
|
@if (entityName) {
|
|
<div class="entity-info">
|
|
<fa-icon icon="building" class="entity-icon"></fa-icon>
|
|
<div class="entity-detail">
|
|
<span class="entity-label">Entity</span>
|
|
<span class="entity-name">{{ entityName }}</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
<div class="din-info-bar">
|
|
<fa-icon icon="info-circle" class="info-icon"></fa-icon>
|
|
<span
|
|
>Enter the <strong>Director Identification Number (DIN)</strong> 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.</span
|
|
>
|
|
</div>
|
|
<mat-form-field appearance="outline" class="din-input-field">
|
|
<mat-label>DIN Number</mat-label>
|
|
<mat-icon matPrefix class="din-prefix-icon">badge</mat-icon>
|
|
<input
|
|
matInput
|
|
[(ngModel)]="dinNumber"
|
|
placeholder="e.g. 01234567"
|
|
maxlength="8"
|
|
(keyup.enter)="onVerify()"
|
|
autocomplete="off"
|
|
/>
|
|
<mat-hint>8-digit DIN number from MCA</mat-hint>
|
|
</mat-form-field>
|
|
</div>
|
|
}
|
|
|
|
<!-- Step 2: Verifying -->
|
|
@if (verifying) {
|
|
<div class="progress-section">
|
|
<mat-progress-bar mode="determinate" [value]="progress" color="primary"></mat-progress-bar>
|
|
<div class="progress-percent">{{ progress }}%</div>
|
|
<div class="progress-steps">
|
|
@for (step of progressSteps; track step.label; let i = $index) {
|
|
<div class="progress-step" [class.active]="i === currentStep" [class.done]="i < currentStep">
|
|
<fa-icon
|
|
[icon]="i < currentStep ? 'check-circle' : step.icon"
|
|
[class.done-icon]="i < currentStep"
|
|
></fa-icon>
|
|
<span>{{ step.label }}</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
<p class="verify-msg">
|
|
Verifying DIN <strong>{{ dinNumber }}</strong> for <strong>{{ signatoryName }}</strong> against
|
|
<strong>MCA (Ministry of Corporate Affairs)</strong> records.
|
|
</p>
|
|
</div>
|
|
}
|
|
|
|
<!-- Step 3a: Verified -->
|
|
@if (!verifying && verified) {
|
|
<div class="result-card success-card">
|
|
<div class="result-icon-ring success-ring">
|
|
<fa-icon icon="check-circle" class="result-shield success-shield"></fa-icon>
|
|
</div>
|
|
<div class="result-badge success-badge">VERIFIED</div>
|
|
<h3 class="result-title">DIN Verified Successfully</h3>
|
|
<p class="result-detail">
|
|
DIN <strong class="din-highlight">{{ dinNumber }}</strong> for <strong>{{ signatoryName }}</strong> has been
|
|
verified against <strong>MCA</strong> records.
|
|
</p>
|
|
<div class="result-meta">
|
|
<div class="meta-row">
|
|
<span class="meta-label">DIN</span>
|
|
<span class="meta-value meta-mono">{{ dinNumber }}</span>
|
|
</div>
|
|
@if (directorName) {
|
|
<div class="meta-row">
|
|
<span class="meta-label">Director Name</span>
|
|
<span class="meta-value">{{ directorName }}</span>
|
|
</div>
|
|
}
|
|
<div class="meta-row">
|
|
<span class="meta-label">Status</span>
|
|
<span class="meta-value meta-success">{{ dinStatus || 'Active' }}</span>
|
|
</div>
|
|
<div class="meta-row">
|
|
<span class="meta-label">Verified On</span>
|
|
<span class="meta-value">{{ verifiedDate }}</span>
|
|
</div>
|
|
@if (dinDesignation) {
|
|
<div class="meta-row">
|
|
<span class="meta-label">Designation</span>
|
|
<span class="meta-value">{{ dinDesignation }}</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Company Match Section -->
|
|
@if (companies.length > 0) {
|
|
<div class="company-match-section">
|
|
<h4 class="company-match-title">
|
|
<fa-icon icon="building" class="company-title-icon"></fa-icon>
|
|
Associated Companies ({{ companies.length }})
|
|
</h4>
|
|
|
|
@if (companyMatch) {
|
|
<div class="match-banner match-success">
|
|
<fa-icon icon="check-circle" class="match-banner-icon"></fa-icon>
|
|
<div>
|
|
<strong>Entity Match Found</strong>
|
|
<p>
|
|
<strong>{{ entityName }}</strong> matches <strong>{{ matchedCompany }}</strong>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
} @else {
|
|
<div class="match-banner match-warning">
|
|
<fa-icon icon="exclamation-triangle" class="match-banner-icon"></fa-icon>
|
|
<div>
|
|
<strong>No Entity Match</strong>
|
|
<p>
|
|
<strong>{{ entityName }}</strong> does not match any of the director's associated companies.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="company-list">
|
|
@for (company of companies; track company.companyName || $index) {
|
|
<div class="company-item" [class.company-matched]="company._matched">
|
|
@if (company._matched) {
|
|
<mat-icon class="company-match-icon match">check_circle</mat-icon>
|
|
} @else {
|
|
<mat-icon class="company-match-icon no-match">radio_button_unchecked</mat-icon>
|
|
}
|
|
<div class="company-info">
|
|
<span class="company-name">{{ company.companyName }}</span>
|
|
<span class="company-meta">
|
|
@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 }}
|
|
}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<!-- Step 3b: Verification Failed -->
|
|
@if (!verifying && failed) {
|
|
<div class="result-card fail-card">
|
|
<div class="result-icon-ring fail-ring">
|
|
<fa-icon icon="times-circle" class="result-shield fail-shield"></fa-icon>
|
|
</div>
|
|
<div class="result-badge fail-badge">FAILED</div>
|
|
<h3 class="result-title">DIN Verification Failed</h3>
|
|
<p class="result-detail">
|
|
DIN <strong class="din-highlight-fail">{{ dinNumber }}</strong> could not be verified.
|
|
</p>
|
|
@if (failReason) {
|
|
<div class="fail-reason">
|
|
<fa-icon icon="exclamation-triangle" class="fail-reason-icon"></fa-icon>
|
|
<span>{{ failReason }}</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<!-- Step 3c: Error -->
|
|
@if (!verifying && errorOccurred) {
|
|
<div class="result-card error-card">
|
|
<fa-icon icon="exclamation-circle" class="error-icon-large"></fa-icon>
|
|
<p class="error-msg">Verification failed. Please try again.</p>
|
|
<p class="error-detail">{{ errorMessage }}</p>
|
|
</div>
|
|
}
|
|
</mat-dialog-content>
|
|
|
|
<mat-dialog-actions align="end">
|
|
@if (!verifying && !verified && !failed && !errorOccurred) {
|
|
<button mat-stroked-button (click)="onSkip()">Skip</button>
|
|
<button mat-raised-button color="primary" [disabled]="!dinNumber || dinNumber.length < 8" (click)="onVerify()">
|
|
<fa-icon icon="search"></fa-icon> Verify DIN
|
|
</button>
|
|
}
|
|
@if (!verifying && verified) {
|
|
<button mat-raised-button color="primary" (click)="onClose(true)"><fa-icon icon="check"></fa-icon> Done</button>
|
|
}
|
|
@if (!verifying && failed) {
|
|
<button mat-stroked-button (click)="onRetry()">Retry</button>
|
|
<button mat-raised-button color="warn" (click)="onClose(false)"><fa-icon icon="times"></fa-icon> Close</button>
|
|
}
|
|
@if (!verifying && errorOccurred) {
|
|
<button mat-stroked-button (click)="onRetry()">Retry</button>
|
|
<button mat-raised-button (click)="onClose(false)">Close</button>
|
|
}
|
|
</mat-dialog-actions>
|
|
`,
|
|
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<DinVerifyDialogComponent>);
|
|
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);
|
|
}
|
|
}
|
|
}
|