817 lines
35 KiB
HTML
817 lines
35 KiB
HTML
<!--
|
|
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="kyc-tab-container">
|
|
<!-- Loading State -->
|
|
@if (loading) {
|
|
<div class="kyc-loading">
|
|
<mat-progress-spinner mode="indeterminate" diameter="48"></mat-progress-spinner>
|
|
<p>{{ 'labels.inputs.KYC Verification' | translate }}...</p>
|
|
</div>
|
|
}
|
|
|
|
@if (!loading) {
|
|
<!-- Header -->
|
|
<div class="kyc-tab-header">
|
|
<div class="kyc-tab-title">
|
|
<h3>{{ 'labels.inputs.KYC Verification' | translate }}</h3>
|
|
<span class="kyc-badge">KYC</span>
|
|
@if (kycData?.kycStatusCode) {
|
|
<span class="kyc-overall-status" [class]="'status-' + getStatusClass(kycData.kycStatusCode)">
|
|
{{ getStatusLabel(kycData.kycStatusCode) }}
|
|
</span>
|
|
}
|
|
</div>
|
|
<div class="kyc-header-actions">
|
|
@if (!isEditMode && kycData) {
|
|
<button mat-stroked-button class="kyc-print-btn" type="button" (click)="printSelfDeclaration()">
|
|
<fa-icon icon="file-pdf" class="m-r-6"></fa-icon> Self Declaration
|
|
</button>
|
|
}
|
|
<button mat-raised-button color="primary" (click)="toggleEditMode()" type="button">
|
|
<fa-icon [icon]="isEditMode ? 'times' : 'edit'" class="m-r-10"></fa-icon>
|
|
{{ isEditMode ? ('labels.buttons.Cancel' | translate) : ('labels.buttons.Edit' | translate) }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- KYC Summary (read-only when not in edit mode) -->
|
|
@if (!isEditMode && kycData) {
|
|
@if (kycData.isException && !kycData.exceptionResolved) {
|
|
<div class="kyc-exception-banner" style="background: #fef3c7; border: 1px solid #f59e0b; border-radius: 8px; padding: 16px; margin-bottom: 20px; display: flex; align-items: flex-start; gap: 12px;">
|
|
<mat-icon style="color: #d97706; margin-top: 2px;">warning</mat-icon>
|
|
<div>
|
|
<strong style="color: #92400e; font-size: 14px;">KYC Exception Active</strong>
|
|
<p style="color: #b45309; font-size: 13px; margin: 4px 0 0 0;">
|
|
Account is open under a temporary KYC Exception. Reason: <strong>{{ kycData.exceptionReason || 'Other' }}</strong>.
|
|
Please contact the customer and complete verification as soon as possible.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<!-- Verification Table -->
|
|
<div class="kv-table">
|
|
<div class="kv-table__header">
|
|
<span class="kv-table__col kv-table__col--doc">Document</span>
|
|
<span class="kv-table__col kv-table__col--num">Number</span>
|
|
<span class="kv-table__col kv-table__col--status">Status</span>
|
|
<span class="kv-table__col kv-table__col--date">Verified On</span>
|
|
<span class="kv-table__col kv-table__col--action">Action</span>
|
|
</div>
|
|
|
|
<!-- PAN Row -->
|
|
<div class="kv-table__row">
|
|
<span class="kv-table__col kv-table__col--doc">
|
|
<mat-icon class="kv-doc-icon">credit_card</mat-icon> PAN
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--num kv-mono">{{ kycData.panNumber || '—' }}</span>
|
|
<span class="kv-table__col kv-table__col--status">
|
|
@if (kycData.panVerified) {
|
|
<span class="kv-status kv-status--ok"><mat-icon>check_circle</mat-icon> Verified</span>
|
|
} @else {
|
|
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
|
}
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--date">{{
|
|
kycData.panVerifiedOn ? formatDate(kycData.panVerifiedOn) : '—'
|
|
}}</span>
|
|
<span class="kv-table__col kv-table__col--action">
|
|
<button
|
|
mat-stroked-button
|
|
class="kv-action-btn"
|
|
type="button"
|
|
[disabled]="verifying.pan || !kycData.panNumber"
|
|
(click)="verifyPAN()"
|
|
>
|
|
@if (verifying.pan) {
|
|
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
Re-Verify
|
|
}
|
|
</button>
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Aadhaar Row -->
|
|
<div class="kv-table__row">
|
|
<span class="kv-table__col kv-table__col--doc">
|
|
<mat-icon class="kv-doc-icon">fingerprint</mat-icon> Aadhaar
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--num kv-mono">{{ kycData.aadhaarNumberMasked || '—' }}</span>
|
|
<span class="kv-table__col kv-table__col--status">
|
|
@if (kycData.aadhaarVerified) {
|
|
<span class="kv-status kv-status--ok"><mat-icon>check_circle</mat-icon> Verified</span>
|
|
} @else {
|
|
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
|
}
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--date">{{
|
|
kycData.aadhaarVerifiedOn ? formatDate(kycData.aadhaarVerifiedOn) : '—'
|
|
}}</span>
|
|
<span class="kv-table__col kv-table__col--action">
|
|
@if (!showAadhaarReVerify && !aadhaarOtpSent) {
|
|
<button
|
|
mat-stroked-button
|
|
class="kv-action-btn"
|
|
type="button"
|
|
[disabled]="verifying.aadhaar || !kycData.aadhaarNumberMasked"
|
|
(click)="startAadhaarReVerify()"
|
|
>
|
|
Re-Verify
|
|
</button>
|
|
}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- GSTIN Row (Non-Individual only) -->
|
|
@if (isNonIndividual) {
|
|
<div class="kv-table__row">
|
|
<span class="kv-table__col kv-table__col--doc">
|
|
<mat-icon class="kv-doc-icon">business</mat-icon> GSTIN
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--num kv-mono">{{ kycData.gstinNumber || '—' }}</span>
|
|
<span class="kv-table__col kv-table__col--status">
|
|
@if (kycData.gstinVerified) {
|
|
<span class="kv-status kv-status--ok"><mat-icon>check_circle</mat-icon> Verified</span>
|
|
} @else {
|
|
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
|
}
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--date">{{
|
|
kycData.gstinVerifiedOn ? formatDate(kycData.gstinVerifiedOn) : '—'
|
|
}}</span>
|
|
<span class="kv-table__col kv-table__col--action">
|
|
<button
|
|
mat-stroked-button
|
|
class="kv-action-btn"
|
|
type="button"
|
|
[disabled]="verifying.gstin || !kycData.gstinNumber"
|
|
(click)="verifyGSTIN()"
|
|
>
|
|
@if (verifying.gstin) {
|
|
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
Re-Verify
|
|
}
|
|
</button>
|
|
</span>
|
|
</div>
|
|
}
|
|
|
|
<!-- Enterprise PAN Row (Non-Individual) -->
|
|
@if (isNonIndividual && kycData?.enterprisePan) {
|
|
<div class="kv-table__row">
|
|
<span class="kv-table__col kv-table__col--doc">
|
|
<mat-icon class="kv-doc-icon">credit_card</mat-icon> Enterprise PAN
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--num kv-mono">{{ kycData.enterprisePan }}</span>
|
|
<span class="kv-table__col kv-table__col--status">
|
|
@if (kycData.enterprisePanVerified) {
|
|
<span class="kv-status kv-status--ok"><mat-icon>check_circle</mat-icon> Verified</span>
|
|
} @else {
|
|
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
|
}
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--date">{{
|
|
kycData.enterprisePanVerifiedOn ? formatDate(kycData.enterprisePanVerifiedOn) : '—'
|
|
}}</span>
|
|
<span class="kv-table__col kv-table__col--action"></span>
|
|
</div>
|
|
}
|
|
|
|
<!-- CIN Row (Non-Individual / Company) -->
|
|
@if (isNonIndividual && kycData?.cinNumber) {
|
|
<div class="kv-table__row">
|
|
<span class="kv-table__col kv-table__col--doc">
|
|
<mat-icon class="kv-doc-icon">verified_user</mat-icon> CIN
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--num kv-mono">{{ kycData.cinNumber }}</span>
|
|
<span class="kv-table__col kv-table__col--status">
|
|
@if (kycData.cinVerified) {
|
|
<span class="kv-status kv-status--ok"><mat-icon>check_circle</mat-icon> Verified</span>
|
|
} @else {
|
|
<span class="kv-status kv-status--pending"><mat-icon>radio_button_unchecked</mat-icon> Pending</span>
|
|
}
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--date">—</span>
|
|
<span class="kv-table__col kv-table__col--action"></span>
|
|
</div>
|
|
}
|
|
|
|
<!-- Incorporation Date Row (Non-Individual) -->
|
|
@if (isNonIndividual && kycData?.incorporationDate) {
|
|
<div class="kv-table__row kv-table__row--link">
|
|
<span class="kv-table__col kv-table__col--doc">
|
|
<mat-icon class="kv-doc-icon">event</mat-icon> Incorporation Date
|
|
</span>
|
|
<span class="kv-table__col kv-table__col--num">{{ formatDate(kycData.incorporationDate) }}</span>
|
|
<span class="kv-table__col kv-table__col--status">—</span>
|
|
<span class="kv-table__col kv-table__col--date">—</span>
|
|
<span class="kv-table__col kv-table__col--action"></span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- ─── PAN ↔ Aadhaar Link — premium status card (replaces the legacy
|
|
table row). Dark gradient base with state-driven accent so the
|
|
linking state is the first thing a reviewer sees. -->
|
|
<div
|
|
class="pal-card"
|
|
[class.pal-card--ok]="kycData?.panAadhaarSeedingStatus === 'Y'"
|
|
[class.pal-card--fail]="kycData?.panAadhaarSeedingStatus === 'N'"
|
|
[class.pal-card--info]="kycData?.panAadhaarSeedingStatus === 'E'"
|
|
[class.pal-card--pending]="
|
|
kycData?.panAadhaarSeedingStatus !== 'Y' &&
|
|
kycData?.panAadhaarSeedingStatus !== 'N' &&
|
|
kycData?.panAadhaarSeedingStatus !== 'E'
|
|
"
|
|
>
|
|
<div class="pal-card__left">
|
|
<div class="pal-card__icon">
|
|
<mat-icon>credit_card</mat-icon>
|
|
</div>
|
|
<div class="pal-card__txt">
|
|
<div class="pal-card__small">Verification Status</div>
|
|
<div class="pal-card__title">PAN ↔ Aadhaar</div>
|
|
<div class="pal-card__status">
|
|
@if (kycData?.panAadhaarSeedingStatus === 'Y') {
|
|
<mat-icon>check_circle</mat-icon> Linked (Seeded)
|
|
} @else if (kycData?.panAadhaarSeedingStatus === 'N') {
|
|
<mat-icon>cancel</mat-icon> Not Linked
|
|
} @else if (kycData?.panAadhaarSeedingStatus === 'E') {
|
|
<mat-icon>info</mat-icon> Exempt
|
|
} @else {
|
|
<mat-icon>radio_button_unchecked</mat-icon> Pending
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="pal-card__badge">
|
|
@if (kycData?.panAadhaarSeedingStatus === 'Y') {
|
|
<mat-icon>check</mat-icon>
|
|
} @else if (kycData?.panAadhaarSeedingStatus === 'N') {
|
|
<mat-icon>close</mat-icon>
|
|
} @else if (kycData?.panAadhaarSeedingStatus === 'E') {
|
|
<mat-icon>info</mat-icon>
|
|
} @else {
|
|
<mat-icon>schedule</mat-icon>
|
|
}
|
|
</div>
|
|
<div class="pal-card__hint">As per Income Tax Portal — Section 139AA</div>
|
|
</div>
|
|
|
|
<!-- Aadhaar OTP inline (shown below table when re-verifying) -->
|
|
@if (showAadhaarReVerify && !aadhaarOtpSent) {
|
|
<div class="kv-otp-bar">
|
|
<mat-form-field appearance="outline" class="kv-otp-input">
|
|
<mat-label>Enter 12-digit Aadhaar</mat-label>
|
|
<input
|
|
matInput
|
|
[(ngModel)]="aadhaarFullNumber"
|
|
[ngModelOptions]="{ standalone: true }"
|
|
maxlength="12"
|
|
type="password"
|
|
/>
|
|
</mat-form-field>
|
|
<button
|
|
mat-raised-button
|
|
color="primary"
|
|
type="button"
|
|
class="kv-otp-btn"
|
|
[disabled]="!aadhaarFullNumber || aadhaarFullNumber.length !== 12 || verifying.aadhaar"
|
|
(click)="sendAadhaarOtp()"
|
|
>
|
|
@if (verifying.aadhaar) {
|
|
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
Send OTP
|
|
}
|
|
</button>
|
|
</div>
|
|
}
|
|
@if (aadhaarOtpSent) {
|
|
<div class="kv-otp-bar">
|
|
<span class="kv-otp-label"><mat-icon>sms</mat-icon> OTP sent</span>
|
|
<mat-form-field appearance="outline" class="kv-otp-input">
|
|
<mat-label>Enter OTP</mat-label>
|
|
<input
|
|
matInput
|
|
[(ngModel)]="aadhaarOtp"
|
|
[ngModelOptions]="{ standalone: true }"
|
|
maxlength="6"
|
|
autocomplete="one-time-code"
|
|
/>
|
|
</mat-form-field>
|
|
<button
|
|
mat-raised-button
|
|
color="primary"
|
|
type="button"
|
|
class="kv-otp-btn"
|
|
[disabled]="!aadhaarOtp || aadhaarOtp.length < 4 || aadhaarOtpVerifying"
|
|
(click)="verifyAadhaarOtp()"
|
|
>
|
|
@if (aadhaarOtpVerifying) {
|
|
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
Verify
|
|
}
|
|
</button>
|
|
<button
|
|
mat-stroked-button
|
|
type="button"
|
|
class="kv-otp-btn"
|
|
[disabled]="verifying.aadhaar"
|
|
(click)="sendAadhaarOtp()"
|
|
>
|
|
Resend
|
|
</button>
|
|
</div>
|
|
}
|
|
@if (verificationError.aadhaar) {
|
|
<div class="kyc-error-msg">
|
|
<mat-icon class="kyc-error-icon">error_outline</mat-icon> {{ verificationError.aadhaar }}
|
|
</div>
|
|
}
|
|
@if (verificationError.pan) {
|
|
<div class="kyc-error-msg">
|
|
<mat-icon class="kyc-error-icon">error_outline</mat-icon> {{ verificationError.pan }}
|
|
</div>
|
|
}
|
|
|
|
<!-- KYC Meta Grid -->
|
|
<div class="kv-meta-grid">
|
|
<div class="kv-meta-item">
|
|
<span class="kv-meta-label">KYC Type</span>
|
|
<span class="kv-meta-value">{{
|
|
kycData.kycType === 1 ? 'Individual' : kycData.kycType === 2 ? 'Non-Individual' : '—'
|
|
}}</span>
|
|
</div>
|
|
<div class="kv-meta-item">
|
|
<span class="kv-meta-label">Risk Category</span>
|
|
<span class="kv-meta-value">{{
|
|
kycData.riskCategory === 1
|
|
? 'Low'
|
|
: kycData.riskCategory === 2
|
|
? 'Medium'
|
|
: kycData.riskCategory === 3
|
|
? 'High'
|
|
: '—'
|
|
}}</span>
|
|
</div>
|
|
<div class="kv-meta-item">
|
|
<span class="kv-meta-label">Initiated On</span>
|
|
<span class="kv-meta-value">{{ formatDate(kycData.initiatedOn) }}</span>
|
|
</div>
|
|
<div class="kv-meta-item">
|
|
<span class="kv-meta-label">Last Verified</span>
|
|
<span class="kv-meta-value">{{ formatDate(kycData.verifiedOn) }}</span>
|
|
</div>
|
|
<div class="kv-meta-item">
|
|
<span class="kv-meta-label">Expiry Date</span>
|
|
<span class="kv-meta-value">{{ formatDate(kycData.expiryDate) }}</span>
|
|
</div>
|
|
@if (kycData.remarks) {
|
|
<div class="kv-meta-item kv-meta-item--full">
|
|
<span class="kv-meta-label">Remarks</span>
|
|
<span class="kv-meta-value">{{ kycData.remarks }}</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- Re-KYC: Upload Signed Declaration & Complete -->
|
|
<div class="rekyc-section">
|
|
<div class="rekyc-header">
|
|
<div class="rekyc-icon-wrap">
|
|
<fa-icon icon="sync-alt" class="rekyc-icon"></fa-icon>
|
|
</div>
|
|
<div class="rekyc-header-text">
|
|
<h4 class="rekyc-title">Re-KYC Completion</h4>
|
|
<p class="rekyc-desc">Print self-declaration, get it signed, upload & complete re-KYC.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 1 -->
|
|
<div class="rekyc-timeline">
|
|
<div class="rekyc-tl-item">
|
|
<div class="rekyc-tl-dot">
|
|
<span class="rekyc-tl-num">1</span>
|
|
<div class="rekyc-tl-line"></div>
|
|
</div>
|
|
<div class="rekyc-tl-content">
|
|
<div class="rekyc-tl-label">Download Self Declaration Form</div>
|
|
<div class="rekyc-tl-sub">System-generated PDF with PAN, Aadhaar & KYC details</div>
|
|
<button mat-flat-button class="rekyc-btn-print" type="button" (click)="printSelfDeclaration()">
|
|
<fa-icon icon="file-pdf"></fa-icon> Download PDF
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2 -->
|
|
<div class="rekyc-tl-item">
|
|
<div class="rekyc-tl-dot">
|
|
<span class="rekyc-tl-num">2</span>
|
|
<div class="rekyc-tl-line"></div>
|
|
</div>
|
|
<div class="rekyc-tl-content">
|
|
<div class="rekyc-tl-label">Upload Signed Declaration</div>
|
|
<div class="rekyc-tl-sub">Get the form signed by the client and upload the scanned copy</div>
|
|
<label class="rekyc-upload-zone" [class.has-file]="selectedDeclarationFile">
|
|
<input type="file" accept=".pdf,.jpg,.jpeg,.png" (change)="onDeclarationFileSelected($event)" hidden />
|
|
@if (!selectedDeclarationFile) {
|
|
<mat-icon class="upload-zone-icon">cloud_upload</mat-icon>
|
|
<span class="upload-zone-text">Click to upload signed form</span>
|
|
<span class="upload-zone-hint">PDF, JPG or PNG</span>
|
|
} @else {
|
|
<fa-icon icon="check-circle" class="upload-zone-check"></fa-icon>
|
|
<span class="upload-zone-filename">{{ selectedDeclarationFile.name }}</span>
|
|
<span class="upload-zone-hint">Click to change file</span>
|
|
}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3 -->
|
|
<div class="rekyc-tl-item rekyc-tl-item--last">
|
|
<div class="rekyc-tl-dot">
|
|
<span class="rekyc-tl-num">3</span>
|
|
</div>
|
|
<div class="rekyc-tl-content">
|
|
<div class="rekyc-tl-label">Complete Re-KYC</div>
|
|
<div class="rekyc-tl-sub">Updates Last Verified date and sets new Expiry Date as per RBI norms</div>
|
|
<button
|
|
mat-flat-button
|
|
color="primary"
|
|
class="rekyc-btn-complete"
|
|
type="button"
|
|
[disabled]="!selectedDeclarationFile || reKycCompleting || !kycId"
|
|
(click)="completeReKyc()"
|
|
>
|
|
@if (reKycCompleting) {
|
|
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
|
Completing...
|
|
} @else {
|
|
<fa-icon icon="check-circle"></fa-icon> Complete Re-KYC
|
|
}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (reKycSuccess) {
|
|
<div class="rekyc-result rekyc-result--success">
|
|
<fa-icon icon="check-circle" class="rekyc-result-icon"></fa-icon>
|
|
<div>
|
|
<strong>Re-KYC completed successfully</strong>
|
|
<p>Last Verified and Expiry Date have been updated.</p>
|
|
</div>
|
|
</div>
|
|
}
|
|
@if (reKycError) {
|
|
<div class="rekyc-result rekyc-result--error">
|
|
<mat-icon class="rekyc-result-icon">error_outline</mat-icon>
|
|
<div>
|
|
<strong>Re-KYC failed</strong>
|
|
<p>{{ reKycError }}</p>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<!-- No KYC Data State -->
|
|
@if (!isEditMode && !kycData) {
|
|
<div class="kyc-empty">
|
|
<mat-icon class="kyc-empty-icon">verified_user</mat-icon>
|
|
<p>{{ 'labels.inputs.KYC Verification' | translate }} not initiated yet.</p>
|
|
<button mat-raised-button color="primary" (click)="toggleEditMode()" type="button">
|
|
{{ 'labels.inputs.KYC verification initiated' | translate }}
|
|
</button>
|
|
</div>
|
|
}
|
|
|
|
<!-- Edit Form -->
|
|
@if (isEditMode) {
|
|
<mat-card class="kyc-edit-card">
|
|
<mat-card-content>
|
|
<form [formGroup]="kycForm" (ngSubmit)="saveKyc()">
|
|
<div class="kyc-edit-grid">
|
|
<!-- PAN -->
|
|
<div class="kyc-edit-field">
|
|
<div class="kyc-field-label-row">
|
|
<span class="kyc-field-title">{{ 'labels.inputs.PAN Number' | translate }}</span>
|
|
<span class="kyc-tag">KYC</span>
|
|
</div>
|
|
<div class="kyc-input-row">
|
|
<mat-form-field appearance="outline" class="kyc-input">
|
|
<mat-label>{{ 'labels.inputs.PAN Number' | translate }}</mat-label>
|
|
<input
|
|
matInput
|
|
formControlName="panNumber"
|
|
maxlength="10"
|
|
style="text-transform: uppercase"
|
|
(input)="
|
|
kycForm
|
|
.get('panNumber')
|
|
?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
|
|
"
|
|
/>
|
|
@if (kycForm.get('panNumber')?.hasError('pattern') && kycForm.get('panNumber')?.touched) {
|
|
<mat-error>{{ 'labels.inputs.Enter 10-character PAN' | translate }}</mat-error>
|
|
}
|
|
@if (verificationStatus.pan === 'VERIFIED') {
|
|
<mat-icon matSuffix class="status-icon verified">check_circle</mat-icon>
|
|
}
|
|
@if (verificationStatus.pan === 'FAILED') {
|
|
<mat-icon matSuffix class="status-icon failed">cancel</mat-icon>
|
|
}
|
|
</mat-form-field>
|
|
<button
|
|
mat-raised-button
|
|
color="accent"
|
|
type="button"
|
|
class="verify-btn"
|
|
[disabled]="
|
|
!kycId || !kycForm.get('panNumber')?.value || kycForm.get('panNumber')?.invalid || verifying.pan
|
|
"
|
|
(click)="verifyPAN()"
|
|
[matTooltip]="!kycId ? 'Save KYC details first' : ''"
|
|
>
|
|
@if (verifying.pan) {
|
|
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
{{ 'labels.inputs.Verify' | translate }}
|
|
}
|
|
</button>
|
|
</div>
|
|
@if (verificationError.pan) {
|
|
<div class="kyc-error-msg">
|
|
<mat-icon class="kyc-error-icon">error_outline</mat-icon>
|
|
<span>{{ verificationError.pan }}</span>
|
|
</div>
|
|
}
|
|
@if (verificationStatus.pan === 'VERIFIED' && verificationDetails.pan?.name) {
|
|
<span class="verification-detail-text">{{ verificationDetails.pan.name }}</span>
|
|
}
|
|
</div>
|
|
|
|
<!-- Aadhaar -->
|
|
<div class="kyc-edit-field">
|
|
<div class="kyc-field-label-row">
|
|
<span class="kyc-field-title">{{ 'labels.inputs.Aadhaar Number' | translate }}</span>
|
|
<span class="kyc-tag">KYC</span>
|
|
</div>
|
|
<div class="kyc-input-row">
|
|
<mat-form-field appearance="outline" class="kyc-input">
|
|
<mat-label>{{ 'labels.inputs.Aadhaar Number' | translate }}</mat-label>
|
|
<input
|
|
matInput
|
|
formControlName="aadhaarNumber"
|
|
maxlength="12"
|
|
inputmode="numeric"
|
|
autocomplete="off"
|
|
placeholder="Enter 12-digit Aadhaar"
|
|
[readonly]="aadhaarOtpSent"
|
|
/>
|
|
@if (kycData?.aadhaarNumberMasked) {
|
|
<mat-hint>On file: {{ kycData.aadhaarNumberMasked }}</mat-hint>
|
|
}
|
|
@if (kycForm.get('aadhaarNumber')?.hasError('pattern') && kycForm.get('aadhaarNumber')?.touched) {
|
|
<mat-error>{{ 'labels.inputs.Enter 12-digit Aadhaar' | translate }}</mat-error>
|
|
}
|
|
@if (verificationStatus.aadhaar === 'VERIFIED') {
|
|
<mat-icon matSuffix class="status-icon verified">check_circle</mat-icon>
|
|
}
|
|
@if (verificationStatus.aadhaar === 'FAILED') {
|
|
<mat-icon matSuffix class="status-icon failed">cancel</mat-icon>
|
|
}
|
|
</mat-form-field>
|
|
@if (!aadhaarOtpSent) {
|
|
<button
|
|
mat-raised-button
|
|
color="accent"
|
|
type="button"
|
|
class="verify-btn"
|
|
[disabled]="
|
|
!kycId ||
|
|
!kycForm.get('aadhaarNumber')?.value ||
|
|
kycForm.get('aadhaarNumber')?.invalid ||
|
|
verifying.aadhaar
|
|
"
|
|
(click)="sendAadhaarOtp()"
|
|
[matTooltip]="!kycId ? 'Save KYC details first' : ''"
|
|
>
|
|
@if (verifying.aadhaar) {
|
|
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
Send OTP
|
|
}
|
|
</button>
|
|
}
|
|
</div>
|
|
|
|
<!-- OTP Section in Edit Mode -->
|
|
@if (aadhaarOtpSent) {
|
|
<div class="otp-section">
|
|
<p class="otp-info">
|
|
<mat-icon class="otp-info-icon">sms</mat-icon>
|
|
OTP sent to Aadhaar-linked mobile
|
|
</p>
|
|
<div class="otp-inline">
|
|
<mat-form-field appearance="outline" class="otp-field">
|
|
<mat-label>Enter OTP</mat-label>
|
|
<input
|
|
matInput
|
|
[(ngModel)]="aadhaarOtp"
|
|
[ngModelOptions]="{ standalone: true }"
|
|
maxlength="6"
|
|
autocomplete="one-time-code"
|
|
/>
|
|
</mat-form-field>
|
|
<button
|
|
mat-raised-button
|
|
color="accent"
|
|
class="otp-verify-btn"
|
|
type="button"
|
|
[disabled]="!aadhaarOtp || aadhaarOtp.length < 4 || aadhaarOtpVerifying"
|
|
(click)="verifyAadhaarOtp()"
|
|
>
|
|
@if (aadhaarOtpVerifying) {
|
|
<mat-progress-spinner diameter="14" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
Verify OTP
|
|
}
|
|
</button>
|
|
<button
|
|
mat-stroked-button
|
|
type="button"
|
|
class="otp-resend-btn"
|
|
[disabled]="verifying.aadhaar"
|
|
(click)="sendAadhaarOtp()"
|
|
>
|
|
Resend
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
@if (verificationError.aadhaar) {
|
|
<div class="kyc-error-msg">
|
|
<mat-icon class="kyc-error-icon">error_outline</mat-icon>
|
|
<span>{{ verificationError.aadhaar }}</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
<!-- GSTIN (Non-Individual only) -->
|
|
@if (isNonIndividual) {
|
|
<div class="kyc-edit-field">
|
|
<div class="kyc-field-label-row">
|
|
<span class="kyc-field-title">{{ 'labels.inputs.GSTIN Number' | translate }}</span>
|
|
<span class="kyc-tag">KYC</span>
|
|
</div>
|
|
<div class="kyc-input-row">
|
|
<mat-form-field appearance="outline" class="kyc-input">
|
|
<mat-label>{{ 'labels.inputs.GSTIN Number' | translate }}</mat-label>
|
|
<input
|
|
matInput
|
|
formControlName="gstinNumber"
|
|
maxlength="15"
|
|
style="text-transform: uppercase"
|
|
(input)="
|
|
kycForm
|
|
.get('gstinNumber')
|
|
?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
|
|
"
|
|
/>
|
|
@if (kycForm.get('gstinNumber')?.hasError('pattern') && kycForm.get('gstinNumber')?.touched) {
|
|
<mat-error>{{ 'labels.inputs.Enter 15-character GSTIN' | translate }}</mat-error>
|
|
}
|
|
@if (verificationStatus.gstin === 'VERIFIED') {
|
|
<mat-icon matSuffix class="status-icon verified">check_circle</mat-icon>
|
|
}
|
|
@if (verificationStatus.gstin === 'FAILED') {
|
|
<mat-icon matSuffix class="status-icon failed">cancel</mat-icon>
|
|
}
|
|
</mat-form-field>
|
|
<button
|
|
mat-raised-button
|
|
color="accent"
|
|
type="button"
|
|
class="verify-btn"
|
|
[disabled]="
|
|
!kycId ||
|
|
!kycForm.get('gstinNumber')?.value ||
|
|
kycForm.get('gstinNumber')?.invalid ||
|
|
verifying.gstin
|
|
"
|
|
(click)="verifyGSTIN()"
|
|
[matTooltip]="!kycId ? 'Save KYC details first' : ''"
|
|
>
|
|
@if (verifying.gstin) {
|
|
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
{{ 'labels.inputs.Verify' | translate }}
|
|
}
|
|
</button>
|
|
</div>
|
|
@if (verificationError.gstin) {
|
|
<div class="kyc-error-msg">
|
|
<mat-icon class="kyc-error-icon">error_outline</mat-icon>
|
|
<span>{{ verificationError.gstin }}</span>
|
|
</div>
|
|
}
|
|
@if (verificationStatus.gstin === 'VERIFIED' && verificationDetails.gstin?.businessName) {
|
|
<span class="verification-detail-text">{{ verificationDetails.gstin.businessName }}</span>
|
|
}
|
|
</div>
|
|
}
|
|
|
|
<!-- KYC Type -->
|
|
@if (!kycId) {
|
|
<mat-form-field appearance="outline">
|
|
<mat-label>{{ 'labels.inputs.KYC Type' | translate }}</mat-label>
|
|
<mat-select formControlName="kycType">
|
|
<mat-option [value]="1">Individual</mat-option>
|
|
<mat-option [value]="2">Non-Individual</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
}
|
|
|
|
<!-- Risk Category -->
|
|
<mat-form-field appearance="outline">
|
|
<mat-label>{{ 'labels.inputs.Risk Category' | translate }}</mat-label>
|
|
<mat-select formControlName="riskCategory">
|
|
<mat-option [value]="1">Low</mat-option>
|
|
<mat-option [value]="2">Medium</mat-option>
|
|
<mat-option [value]="3">High</mat-option>
|
|
</mat-select>
|
|
</mat-form-field>
|
|
|
|
<!-- KYC Exception Flag -->
|
|
<div class="full-span exception-controls-row" style="display: flex; align-items: center; gap: 20px; margin-bottom: 12px; flex-wrap: wrap;">
|
|
<mat-checkbox formControlName="isException" style="margin-top: 8px;">Mark as KYC Exception</mat-checkbox>
|
|
|
|
@if (kycForm.get('isException')?.value) {
|
|
<mat-form-field appearance="outline" style="flex: 1; min-width: 250px;">
|
|
<mat-label>Exception Reasons</mat-label>
|
|
<mat-select formControlName="exceptionReason" multiple>
|
|
@if (!isNonIndividual) {
|
|
<mat-option value="PAN Card Verification Failed">PAN Card Verification Failed</mat-option>
|
|
<mat-option value="Aadhaar Verification Failed">Aadhaar Verification Failed</mat-option>
|
|
} @else {
|
|
<mat-option value="Enterprise PAN Verification Failed">Enterprise PAN Verification Failed</mat-option>
|
|
<mat-option value="GSTIN Verification Failed">GSTIN Verification Failed</mat-option>
|
|
<mat-option value="CIN Verification Failed">CIN Verification Failed</mat-option>
|
|
}
|
|
</mat-select>
|
|
</mat-form-field>
|
|
}
|
|
</div>
|
|
|
|
<!-- Remarks -->
|
|
<mat-form-field appearance="outline" class="full-span">
|
|
<mat-label>{{ 'labels.inputs.Remarks' | translate }}</mat-label>
|
|
<textarea matInput formControlName="remarks" rows="3"></textarea>
|
|
</mat-form-field>
|
|
|
|
</div>
|
|
|
|
@if (errorMessage) {
|
|
<div class="kyc-error-msg">
|
|
<mat-icon class="kyc-error-icon">error_outline</mat-icon>
|
|
<span>{{ errorMessage }}</span>
|
|
</div>
|
|
}
|
|
|
|
@if (!kycId) {
|
|
<div class="kyc-info-hint">
|
|
<mat-icon class="kyc-info-hint-icon">info</mat-icon>
|
|
<span>Save KYC details first to enable verification buttons.</span>
|
|
</div>
|
|
}
|
|
|
|
<div class="kyc-form-actions">
|
|
<button mat-raised-button type="submit" color="primary" [disabled]="kycForm.invalid || saving">
|
|
@if (saving) {
|
|
<mat-progress-spinner diameter="18" mode="indeterminate"></mat-progress-spinner>
|
|
} @else {
|
|
{{ 'labels.buttons.Submit' | translate }}
|
|
}
|
|
</button>
|
|
<button mat-stroked-button type="button" (click)="toggleEditMode()">
|
|
{{ 'labels.buttons.Cancel' | translate }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</mat-card-content>
|
|
</mat-card>
|
|
}
|
|
}
|
|
</div>
|