Files
cbsnexora/src/app/clients/client-stepper/client-kyc-step/client-kyc-step.component.html
T
keshav 569f17ef46
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s
kyc exception at client creation time and some tds work
2026-08-05 17:23:56 +05:30

1530 lines
68 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--
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="mat-typography" [formGroup]="kycForm">
<div class="step-header">
<div class="step-header__left">
<fa-icon icon="id-card" class="step-header__icon"></fa-icon>
<div>
<h3 class="step-header__title">KYC &amp; Documents</h3>
<p class="step-header__sub">RBI-compliant KYC verification — PAN, Aadhaar &amp; document upload.</p>
</div>
</div>
</div>
<!-- ═══ SECTION: KYC Verification ═══ -->
<div class="section-block">
<h4 class="section-title">
<fa-icon icon="fingerprint" class="section-icon"></fa-icon>
KYC Verification
<span class="kyc-badge">KYC</span>
</h4>
<div class="form-grid">
<!-- ══ INDIVIDUAL KYC ══ -->
@if (!isNonIndividual) {
<!-- Minors don't hold a PAN card — skip the PAN checkbox/section entirely -->
@if (!isMinor) {
@if (readOnly) {
<!-- In view mode the mat-checkbox renders as a faded grey square
that reads as "blurry". Show a clean Yes/No pill instead. -->
<div class="form-field checkbox-field full-width">
<div class="ro-flag">
<span class="ro-flag__label">Client has PAN Card</span>
@if (kycForm.get('hasPan')?.value) {
<span class="ro-flag__chip ro-flag__chip--yes">
<mat-icon>check_circle</mat-icon>
Yes
</span>
} @else {
<span class="ro-flag__chip ro-flag__chip--no">
<mat-icon>cancel</mat-icon>
No
</span>
}
</div>
</div>
} @else {
<div class="form-field checkbox-field full-width">
<mat-checkbox formControlName="hasPan">Client has PAN Card</mat-checkbox>
</div>
}
}
<!-- Individual PAN with inline verify -->
@if (!isMinor && kycForm.get('hasPan')?.value) {
<div class="form-field verify-field">
<mat-form-field appearance="outline" class="verify-input">
<mat-label>PAN Number</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>credit_card</mat-icon></span>
<input
matInput
formControlName="panNumber"
placeholder="e.g. ABCDE1234F"
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>Invalid PAN format</mat-error>
}
@if (panVerifyStatus === 'VERIFIED') {
<mat-icon matSuffix class="verify-icon-ok">check_circle</mat-icon>
}
@if (panVerifyStatus === 'FAILED') {
<mat-icon matSuffix class="verify-icon-fail">cancel</mat-icon>
}
<mat-hint>Format: ABCDE1234F</mat-hint>
</mat-form-field>
<div class="verify-action">
@if (panVerifyStatus === 'NONE' || panVerifyStatus === 'FAILED') {
<button
mat-raised-button
color="primary"
type="button"
class="verify-btn-inline"
[disabled]="
readOnly || !kycForm.get('panNumber')?.value || kycForm.get('panNumber')?.invalid || panVerifying
"
(click)="verifyPan()"
>
@if (panVerifying) {
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
} @else {
<fa-icon icon="shield-alt"></fa-icon> Verify PAN
}
</button>
}
@if (panVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
</div>
</div>
}
<!-- Aadhaar with independent verify (Individual only) -->
<div class="form-field verify-field">
<mat-form-field appearance="outline" class="verify-input">
<mat-label>Aadhaar Number</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>fingerprint</mat-icon></span>
<input
matInput
formControlName="aadhaarNumber"
placeholder="12-digit Aadhaar"
maxlength="12"
[type]="aadhaarVisible ? 'text' : 'password'"
[readonly]="aadhaarOtpSent"
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
/>
<button
mat-icon-button
matSuffix
type="button"
(click)="aadhaarVisible = !aadhaarVisible"
class="toggle-vis-btn"
>
<fa-icon [icon]="aadhaarVisible ? 'eye-slash' : 'eye'"></fa-icon>
</button>
@if (kycForm.get('aadhaarNumber')?.hasError('pattern') && kycForm.get('aadhaarNumber')?.touched) {
<mat-error>Must be 12 digits</mat-error>
}
@if (aadhaarVerifyStatus === 'VERIFIED') {
<mat-icon matSuffix class="verify-icon-ok">check_circle</mat-icon>
}
</mat-form-field>
<div class="verify-action">
@if ((aadhaarVerifyStatus === 'NONE' || aadhaarVerifyStatus === 'FAILED') && !aadhaarOtpSent) {
<button
mat-raised-button
color="primary"
type="button"
class="verify-btn-inline"
[disabled]="
readOnly ||
!kycForm.get('aadhaarNumber')?.value ||
kycForm.get('aadhaarNumber')?.invalid ||
aadhaarVerifying
"
(click)="sendAadhaarOtp()"
>
@if (aadhaarVerifying) {
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
} @else {
<fa-icon icon="shield-alt"></fa-icon> Verify Aadhaar
}
</button>
}
@if (aadhaarOtpSent && aadhaarVerifyStatus !== 'VERIFIED') {
<button
mat-stroked-button
type="button"
class="change-number-btn"
[disabled]="readOnly"
(click)="changeAadhaarNumber()"
>
<mat-icon>edit</mat-icon> Change Number
</button>
}
@if (aadhaarVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
</div>
</div>
<!-- Aadhaar OTP Section -->
@if (aadhaarOtpSent && aadhaarVerifyStatus !== 'VERIFIED') {
<div class="otp-card full-width">
<div class="otp-card__header">
<div class="otp-card__header-icon">
<mat-icon>verified_user</mat-icon>
</div>
<div class="otp-card__header-text">
<span class="otp-card__title">Aadhaar OTP Verification</span>
<span class="otp-card__subtitle">A 6-digit OTP has been sent to the Aadhaar-linked mobile number</span>
</div>
</div>
<div class="otp-card__body">
<label class="otp-card__label">Enter OTP</label>
<div class="otp-digits" (paste)="onOtpPaste($event)">
@for (digit of otpDigits; track $index) {
<input
type="text"
inputmode="numeric"
maxlength="1"
class="otp-digit-box"
[class.filled]="digit"
[value]="digit"
(input)="onOtpDigitInput($index, $event)"
(keydown)="onOtpKeydown($index, $event)"
autocomplete="one-time-code"
/>
}
</div>
<div class="otp-card__actions">
<button
mat-flat-button
color="primary"
type="button"
class="otp-card__verify-btn"
[disabled]="readOnly || aadhaarOtp.length < 6 || aadhaarOtpVerifying"
(click)="verifyAadhaarOtp()"
>
@if (aadhaarOtpVerifying) {
<mat-progress-spinner diameter="16" mode="indeterminate" class="otp-spinner"></mat-progress-spinner>
Verifying...
} @else {
<fa-icon icon="check-circle" class="otp-btn-icon"></fa-icon>
Verify OTP
}
</button>
<div class="otp-card__resend">
@if (resendCountdown > 0) {
<span class="otp-card__countdown">
Resend in <strong>00:{{ resendCountdown.toString().padStart(2, '0') }}</strong>
</span>
} @else {
<span class="otp-card__resend-text">Didn't receive the code?</span>
<button
mat-button
type="button"
class="otp-card__resend-btn"
[disabled]="readOnly || aadhaarVerifying"
(click)="sendAadhaarOtp()"
>
Resend OTP
</button>
}
</div>
</div>
</div>
</div>
}
@if (aadhaarVerifyError) {
<div class="verify-error full-width">
<fa-icon icon="exclamation-circle"></fa-icon> {{ aadhaarVerifyError }}
</div>
}
<!-- ─── Unified Identity Verification Summary ─── -->
<!-- One polished card that consolidates PAN result, Aadhaar result, and -->
<!-- the PAN–Aadhaar link status into a single, scannable panel. -->
@if (panVerifyStatus === 'VERIFIED' || aadhaarVerifyStatus === 'VERIFIED') {
<div
class="idv-summary full-width"
[class.both]="panVerifyStatus === 'VERIFIED' && aadhaarVerifyStatus === 'VERIFIED'"
[class.partial]="
!(panVerifyStatus === 'VERIFIED' && aadhaarVerifyStatus === 'VERIFIED') &&
(panVerifyStatus === 'VERIFIED' || aadhaarVerifyStatus === 'VERIFIED')
"
>
<!-- Hero strip -->
<div class="idv-hero">
<div class="idv-hero__ic"><fa-icon icon="shield-alt"></fa-icon></div>
<div class="idv-hero__txt">
<div class="idv-hero__title">
@if (panVerifyStatus === 'VERIFIED' && aadhaarVerifyStatus === 'VERIFIED') {
Identity Verified
} @else if (panVerifyStatus === 'VERIFIED') {
PAN Verified
} @else {
Aadhaar Verified
}
</div>
<div class="idv-hero__sub">
@if (panVerifyStatus === 'VERIFIED' && aadhaarVerifyStatus === 'VERIFIED') {
Both PAN &amp; Aadhaar confirmed against NSDL &amp; UIDAI records.
} @else if (panVerifyStatus === 'VERIFIED') {
PAN confirmed against NSDL records. Aadhaar verification pending.
} @else {
Aadhaar confirmed via UIDAI OTP. PAN verification pending.
}
</div>
</div>
<div class="idv-hero__count">
<span class="idv-hero__count-num">
{{ (panVerifyStatus === 'VERIFIED' ? 1 : 0) + (aadhaarVerifyStatus === 'VERIFIED' ? 1 : 0) }}
</span>
<span class="idv-hero__count-of">/ 2</span>
</div>
</div>
<!-- Two-column body: PAN | Aadhaar -->
<div class="idv-body">
<!-- PAN column -->
<div class="idv-col" [class.muted]="panVerifyStatus !== 'VERIFIED'">
<div class="idv-col__head">
<fa-icon icon="id-card" class="idv-col__ic"></fa-icon>
<span class="idv-col__label">PAN</span>
@if (panVerifyStatus === 'VERIFIED') {
<span class="idv-chip ok"> <fa-icon icon="check-circle"></fa-icon> Verified </span>
} @else {
<span class="idv-chip pending">Pending</span>
}
</div>
@if (panVerifyStatus === 'VERIFIED') {
<div class="idv-col__rows">
<div class="idv-row">
<span class="idv-row__k">Name on PAN</span>
<span class="idv-row__v name">{{ panVerifyName }}</span>
</div>
<div class="idv-row">
<span class="idv-row__k">PAN Number</span>
<span class="idv-row__v mono">{{ kycForm.get('panNumber')?.value }}</span>
</div>
<div class="idv-row">
<span class="idv-row__k">Verified On</span>
<span class="idv-row__v">{{ panVerifiedOn }}</span>
</div>
</div>
} @else {
<div class="idv-col__empty">Enter PAN above and click <strong>Verify PAN</strong>.</div>
}
</div>
<!-- Aadhaar column -->
<div class="idv-col" [class.muted]="aadhaarVerifyStatus !== 'VERIFIED'">
<div class="idv-col__head">
<fa-icon icon="fingerprint" class="idv-col__ic"></fa-icon>
<span class="idv-col__label">Aadhaar</span>
@if (aadhaarVerifyStatus === 'VERIFIED') {
<span class="idv-chip ok"> <fa-icon icon="check-circle"></fa-icon> Verified </span>
} @else {
<span class="idv-chip pending">Pending</span>
}
</div>
@if (aadhaarVerifyStatus === 'VERIFIED') {
<div class="idv-col__rows">
<div class="idv-row">
<span class="idv-row__k">Name on Aadhaar</span>
<span class="idv-row__v name">{{ aadhaarVerifyName }}</span>
</div>
<div class="idv-row">
<span class="idv-row__k">Aadhaar Number</span>
<span class="idv-row__v mono"
>XXXX XXXX {{ (kycForm.get('aadhaarNumber')?.value || '').slice(-4) }}</span
>
</div>
<div class="idv-row">
<span class="idv-row__k">Channel</span>
<span class="idv-row__v">UIDAI OTP</span>
</div>
</div>
} @else {
<div class="idv-col__empty">Enter Aadhaar above and click <strong>Verify Aadhaar</strong>.</div>
}
</div>
</div>
<!-- PAN ↔ Aadhaar link footer -->
@if (panAadhaarSeedingStatus) {
<div
class="idv-link"
[class.linked]="panAadhaarSeedingStatus === 'Y'"
[class.not-linked]="panAadhaarSeedingStatus === 'N'"
[class.exempt]="panAadhaarSeedingStatus === 'E'"
>
<fa-icon icon="link" class="idv-link__ic"></fa-icon>
<div class="idv-link__txt">
<span class="idv-link__label">PAN &mdash; Aadhaar Link</span>
<span class="idv-link__value">
@if (panAadhaarSeedingStatus === 'Y') {
Linked (Seeded)
} @else if (panAadhaarSeedingStatus === 'N') {
Not Linked
} @else if (panAadhaarSeedingStatus === 'E') {
Exempt
}
</span>
</div>
<span class="idv-link__hint">Income Tax Portal &mdash; Section 139AA</span>
</div>
}
</div>
}
}
<!-- ══ NON-INDIVIDUAL KYC ══ -->
@if (isNonIndividual) {
<!-- Incorporation Date -->
<mat-form-field appearance="outline" class="form-field" (click)="incorpDatePicker.open()">
<mat-label>Incorporation Date</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>calendar_today</mat-icon></span>
<input matInput [max]="today" [matDatepicker]="incorpDatePicker" formControlName="incorporationDate" />
<mat-datepicker-toggle matSuffix [for]="incorpDatePicker"></mat-datepicker-toggle>
<mat-datepicker #incorpDatePicker></mat-datepicker>
</mat-form-field>
<!-- GSTIN (all non-individual) with Verify -->
<div class="form-field verify-field">
<mat-form-field appearance="outline" class="verify-input">
<mat-label>GSTIN Number</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>receipt_long</mat-icon></span>
<input
matInput
formControlName="gstinNumber"
placeholder="15-character GSTIN"
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>Invalid GSTIN format (e.g. 22AAAAA0000A1Z5)</mat-error>
}
@if (gstinVerifyStatus === 'VERIFIED') {
<mat-icon matSuffix class="verify-icon-ok">check_circle</mat-icon>
}
@if (gstinVerifyStatus === 'FAILED') {
<mat-icon matSuffix class="verify-icon-fail">cancel</mat-icon>
}
</mat-form-field>
<div class="verify-action">
@if (gstinVerifyStatus === 'NONE' || gstinVerifyStatus === 'FAILED') {
<button
mat-raised-button
color="primary"
type="button"
class="verify-btn-inline"
[disabled]="
readOnly ||
!kycForm.get('gstinNumber')?.value ||
kycForm.get('gstinNumber')?.value.length < 15 ||
gstinVerifying ||
!clientId
"
(click)="verifyGstin()"
>
@if (gstinVerifying) {
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
} @else {
<fa-icon icon="shield-alt"></fa-icon> Verify GSTIN
}
</button>
}
@if (gstinVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
</div>
</div>
@if (gstinVerifyStatus === 'VERIFIED') {
<div class="pan-result-card full-width">
<div class="pan-result-header">
<fa-icon icon="check-circle" class="pan-result-ok-icon"></fa-icon>
<span>GSTIN Verified Successfully</span>
</div>
<div class="pan-result-body">
@if (gstinVerifyName) {
<div class="pan-result-row">
<span class="pan-result-label">Trade Name / Legal Name</span>
<span class="pan-result-value name">{{ gstinVerifyName }}</span>
</div>
}
@if (gstinVerifyState) {
<div class="pan-result-row">
<span class="pan-result-label">State</span>
<span class="pan-result-value">{{ gstinVerifyState }}</span>
</div>
}
<div class="pan-result-row">
<span class="pan-result-label">Verified On</span>
<span class="pan-result-value date">{{ gstinVerifiedOn }}</span>
</div>
</div>
</div>
}
<!-- PAN — Proprietorship uses Individual PAN, others use Enterprise PAN -->
@if (constitutionMeta?.value === 'PROPRIETORSHIP') {
<!-- Proprietorship: Individual PAN (proprietor's personal PAN) -->
<div class="form-field verify-field">
<mat-form-field appearance="outline" class="verify-input">
<mat-label>PAN of Proprietor</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>credit_card</mat-icon></span>
<input
matInput
formControlName="panNumber"
placeholder="e.g. ABCDE1234F"
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>Invalid PAN format</mat-error>
}
@if (panVerifyStatus === 'VERIFIED') {
<mat-icon matSuffix class="verify-icon-ok">check_circle</mat-icon>
}
@if (panVerifyStatus === 'FAILED') {
<mat-icon matSuffix class="verify-icon-fail">cancel</mat-icon>
}
<mat-hint>PAN of Proprietor (no separate entity PAN needed)</mat-hint>
</mat-form-field>
<div class="verify-action">
@if (panVerifyStatus === 'NONE' || panVerifyStatus === 'FAILED') {
<button
mat-raised-button
color="primary"
type="button"
class="verify-btn-inline"
[disabled]="
readOnly ||
!kycForm.get('panNumber')?.value ||
kycForm.get('panNumber')?.invalid ||
panVerifying ||
!clientId
"
(click)="verifyPan()"
>
@if (panVerifying) {
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
} @else {
<fa-icon icon="shield-alt"></fa-icon> Verify PAN
}
</button>
}
@if (panVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
</div>
</div>
@if (panVerifyStatus === 'VERIFIED') {
<div class="pan-result-card full-width">
<div class="pan-result-header">
<fa-icon icon="check-circle" class="pan-result-ok-icon"></fa-icon>
<span>PAN Verified Successfully</span>
</div>
<div class="pan-result-body">
<div class="pan-result-row">
<span class="pan-result-label">Proprietor Name (as per PAN)</span>
<span class="pan-result-value name">{{ panVerifyName }}</span>
</div>
<div class="pan-result-row">
<span class="pan-result-label">Verified On</span>
<span class="pan-result-value date">{{ panVerifiedOn }}</span>
</div>
</div>
</div>
}
} @else {
<!-- Non-Proprietorship: Enterprise/Company PAN -->
<div class="form-field verify-field">
<mat-form-field appearance="outline" class="verify-input">
<mat-label>{{ constitutionMeta?.panLabel || 'PAN of Entity' }}</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>credit_card</mat-icon></span>
<input
matInput
formControlName="enterprisePan"
placeholder="e.g. ABCDE1234F"
maxlength="10"
style="text-transform: uppercase"
(input)="
kycForm.get('enterprisePan')?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
"
/>
@if (kycForm.get('enterprisePan')?.hasError('pattern') && kycForm.get('enterprisePan')?.touched) {
<mat-error>Invalid PAN format</mat-error>
}
@if (entPanVerifyStatus === 'VERIFIED') {
<mat-icon matSuffix class="verify-icon-ok">check_circle</mat-icon>
}
@if (entPanVerifyStatus === 'FAILED') {
<mat-icon matSuffix class="verify-icon-fail">cancel</mat-icon>
}
<mat-hint>{{ constitutionMeta?.panHint || 'PAN mandatory' }}</mat-hint>
</mat-form-field>
<div class="verify-action">
@if (entPanVerifyStatus === 'NONE' || entPanVerifyStatus === 'FAILED') {
<button
mat-raised-button
color="primary"
type="button"
class="verify-btn-inline"
[disabled]="
readOnly ||
!kycForm.get('enterprisePan')?.value ||
kycForm.get('enterprisePan')?.invalid ||
entPanVerifying ||
!clientId
"
(click)="verifyEnterprisePan()"
>
@if (entPanVerifying) {
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
} @else {
<fa-icon icon="shield-alt"></fa-icon> Verify PAN
}
</button>
}
@if (entPanVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
</div>
</div>
@if (entPanVerifyStatus === 'VERIFIED') {
<div class="pan-result-card full-width">
<div class="pan-result-header">
<fa-icon icon="check-circle" class="pan-result-ok-icon"></fa-icon>
<span>Enterprise PAN Verified</span>
</div>
<div class="pan-result-body">
<div class="pan-result-row">
<span class="pan-result-label">Entity Name (as per PAN)</span>
<span class="pan-result-value name">{{ entPanVerifyName }}</span>
</div>
<div class="pan-result-row">
<span class="pan-result-label">Verified On</span>
<span class="pan-result-value date">{{ entPanVerifiedOn }}</span>
</div>
</div>
</div>
}
}
<!-- CIN (Companies & LLP only) -->
@if (constitutionMeta?.showCin) {
<div class="form-field verify-field">
<mat-form-field appearance="outline" class="verify-input">
<mat-label>CIN (Corporate Identity Number)</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>business</mat-icon></span>
<input
matInput
formControlName="cinNumber"
maxlength="21"
placeholder="From MCA"
style="text-transform: uppercase"
(input)="
kycForm.get('cinNumber')?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
"
/>
@if (kycForm.get('cinNumber')?.hasError('pattern') && kycForm.get('cinNumber')?.touched) {
<mat-error>Invalid CIN format (21 characters)</mat-error>
}
@if (cinVerifyStatus === 'VERIFIED') {
<mat-icon matSuffix class="verify-icon-ok">check_circle</mat-icon>
}
</mat-form-field>
<div class="verify-action">
@if (cinVerifyStatus === 'NONE' || cinVerifyStatus === 'FAILED') {
<button
mat-raised-button
color="primary"
type="button"
class="verify-btn-inline"
[disabled]="readOnly || !kycForm.get('cinNumber')?.value || cinVerifying"
(click)="verifyCin()"
>
@if (cinVerifying) {
<mat-progress-spinner diameter="16" mode="indeterminate"></mat-progress-spinner>
} @else {
<fa-icon icon="shield-alt"></fa-icon> Verify CIN
}
</button>
}
@if (cinVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
</div>
</div>
<!-- CIN verification progress -->
@if (cinVerifying) {
<div class="cin-progress full-width">
<mat-progress-bar mode="determinate" [value]="cinProgressValue" color="primary"></mat-progress-bar>
<div class="cin-progress-percent">{{ cinProgressValue }}%</div>
<div class="cin-progress-steps">
@for (step of cinProgressSteps; track step.label; let i = $index) {
<div class="cin-step" [class.active]="i === cinCurrentStep" [class.done]="i < cinCurrentStep">
<fa-icon
[icon]="i < cinCurrentStep ? 'check-circle' : step.icon"
[class.done-icon]="i < cinCurrentStep"
></fa-icon>
<span>{{ step.label }}</span>
</div>
}
</div>
</div>
}
@if (cinVerifyStatus === 'VERIFIED') {
<div class="pan-result-card full-width">
<div class="pan-result-header">
<fa-icon icon="check-circle" class="pan-result-ok-icon"></fa-icon>
<span>CIN Verified — {{ cinCompanyName }}</span>
</div>
<div class="pan-result-body">
<div class="pan-result-row">
<span class="pan-result-label">CIN</span>
<span class="pan-result-value name">{{ kycForm.get('cinNumber')?.value }}</span>
</div>
<div class="pan-result-row">
<span class="pan-result-label">Verified On</span>
<span class="pan-result-value date">{{ cinVerifiedOn }}</span>
</div>
</div>
</div>
}
}
}
</div>
@if (!clientId) {
<div class="verify-info-banner">
<fa-icon icon="info-circle" class="verify-info-icon"></fa-icon>
<span>PAN verification requires client to be created first. Complete General tab and click Next to save.</span>
</div>
}
<!-- KYC Exception Flag -->
<div class="exception-flag-box" style="margin-top: 20px; padding: 16px; border: 1px solid #f59e0b; background: #fffbeb; border-radius: 8px; clear: both;">
<h5 style="margin: 0 0 12px 0; font-size: 14px; font-weight: 600; color: #b45309; display: flex; align-items: center; gap: 8px;">
<fa-icon icon="exclamation-triangle"></fa-icon>
KYC Exception Flag
</h5>
<div style="display: flex; align-items: center; gap: 20px; flex-wrap: wrap;">
<mat-checkbox formControlName="isException">Mark as KYC Exception</mat-checkbox>
@if (kycForm.get('isException')?.value) {
<mat-form-field appearance="outline" style="flex: 1; min-width: 280px; margin-bottom: -1.25em;">
<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>
</div>
</div>
<!-- ═══ SECTION: KYC Document Management ═══ -->
<div class="section-block">
<h4 class="section-title">
<fa-icon icon="file-alt" class="section-icon"></fa-icon>
KYC Document
<span class="kyc-badge doc-badge">OVD</span>
@if (kycTemplate?.clientTypeName) {
<span class="kyc-client-type-chip">{{ kycTemplate.clientTypeName }}</span>
}
</h4>
<!-- Chip-grouped document type picker -->
<div class="kyc-doc-pickbox">
<div class="kyc-doc-pickbox__head">
<mat-icon class="kyc-doc-pickbox__icon">badge</mat-icon>
<div class="kyc-doc-pickbox__titles">
<p class="kyc-doc-pickbox__title">Identity proof</p>
<p class="kyc-doc-pickbox__sub">Select document type — upload requirements will update automatically</p>
</div>
</div>
<div class="kyc-doc-field">
<label class="kyc-doc-label"><mat-icon>description</mat-icon> Document type</label>
<div class="kyc-doc-chip-wrap" [class.kyc-doc-chip-wrap--locked]="readOnly">
@for (group of kycDocGroups; track group.label; let last = $last) {
<div class="kyc-doc-chip-group-label"><mat-icon>folder_open</mat-icon> {{ group.label }}</div>
<div class="kyc-doc-chip-grid">
@for (p of group.items; track p.id) {
<span
class="kyc-doc-chip"
[class.kyc-doc-chip--sel]="isKycDocSelected(p)"
[class.kyc-doc-chip--locked]="readOnly && !isKycDocSelected(p)"
[attr.aria-disabled]="readOnly ? 'true' : null"
(click)="readOnly ? null : selectKycDoc(p)"
(keydown.enter)="readOnly ? null : selectKycDoc(p)"
[attr.tabindex]="readOnly ? -1 : 0"
role="button"
>
<mat-icon class="kyc-doc-chip__icon">
{{ isKycDocSelected(p) ? 'check_circle' : 'radio_button_unchecked' }}
</mat-icon>
{{ p.name }}
</span>
}
</div>
@if (!last) {
<div class="kyc-doc-chip-divider"></div>
}
}
</div>
@if (kycForm.get('kycDocumentName')?.hasError('required') && kycForm.get('kycDocumentName')?.touched) {
<div class="kyc-doc-error">KYC Document Type is <strong>required</strong></div>
}
</div>
@if (selectedKycDoc?.info) {
<div class="kyc-doc-banner">
<mat-icon>check_circle</mat-icon>
<span>{{ selectedKycDoc.info }}</span>
</div>
}
@if (selectedKycDoc) {
<div class="kyc-doc-upload-grid" [class.kyc-doc-upload-grid--two]="selectedKycDoc.sides === 2">
<div class="kyc-doc-upload-slot">
<div class="kyc-doc-upload-head">
<span class="kyc-doc-upload-title">
<mat-icon>image</mat-icon>
{{ selectedKycDoc.sides === 2 ? 'Front side' : selectedKycDoc.name + ' (single copy)' }}
</span>
<span class="kyc-doc-upload-req">Required</span>
</div>
<!-- Existing saved ID-proof doc — locked by default. Replace flow
unlocks automatically when the section-level Edit button toggles
`readOnly` off. -->
@if (existingKycDocuments.length > 0 && !kycDocFiles.front && readOnly) {
<div class="kyc-doc-dropzone kyc-doc-dropzone--has kyc-doc-dropzone--locked">
<div class="kyc-doc-dropzone__existing">
@if (kycDocMergedPreview) {
<img [src]="kycDocMergedPreview" alt="Saved KYC document" class="kyc-doc-dropzone__thumb" />
} @else {
<mat-icon class="kyc-doc-dropzone__doc-ic">description</mat-icon>
}
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">
{{ existingKycDocuments[0].fileName || existingKycDocuments[0].name }}
</span>
<button type="button" class="kyc-doc-dropzone__view" (click)="viewExistingKycDoc()">
<mat-icon>visibility</mat-icon>
Click here to view
</button>
</div>
</div>
</div>
} @else {
<label
class="kyc-doc-dropzone"
[class.kyc-doc-dropzone--has]="kycDocFiles.front || existingKycDocuments.length > 0"
[class.kyc-doc-dropzone--req]="!kycDocFiles.front && existingKycDocuments.length === 0"
>
<input type="file" accept=".jpg,.jpeg,.png,.pdf" hidden (change)="onKycDocFile($event, 'front')" />
@if (kycDocFiles.front) {
<div class="kyc-doc-dropzone__file">
<mat-icon>insert_drive_file</mat-icon>
<span class="kyc-doc-dropzone__name">{{ kycDocFiles.front.name }}</span>
<button
type="button"
mat-icon-button
class="kyc-doc-dropzone__remove"
(click)="$event.preventDefault(); removeKycDocFile('front')"
>
<mat-icon>close</mat-icon>
</button>
</div>
} @else if (existingKycDocuments.length > 0) {
<!-- Edit mode WITH existing doc — let the user view it OR
click anywhere to replace. -->
<div class="kyc-doc-dropzone__existing">
@if (kycDocMergedPreview) {
<img [src]="kycDocMergedPreview" alt="Saved KYC document" class="kyc-doc-dropzone__thumb" />
} @else {
<mat-icon class="kyc-doc-dropzone__doc-ic">description</mat-icon>
}
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">
{{ existingKycDocuments[0].fileName || existingKycDocuments[0].name }}
</span>
<div class="kyc-doc-dropzone__existing-actions">
<button
type="button"
class="kyc-doc-dropzone__view"
(click)="$event.preventDefault(); $event.stopPropagation(); viewExistingKycDoc()"
>
<mat-icon>visibility</mat-icon>
View
</button>
<span class="kyc-doc-dropzone__sep">·</span>
<span class="kyc-doc-dropzone__existing-hint">
<mat-icon>cloud_upload</mat-icon>
Click here to replace
</span>
</div>
</div>
</div>
} @else {
<div class="kyc-doc-dropzone__hint">
<mat-icon>cloud_upload</mat-icon>
<span>Click to upload (JPG / PNG / PDF · max 5 MB)</span>
</div>
}
</label>
}
</div>
@if (selectedKycDoc.sides === 2) {
<div class="kyc-doc-upload-slot">
<div class="kyc-doc-upload-head">
<span class="kyc-doc-upload-title"> <mat-icon>image</mat-icon> Back side </span>
<!-- Hide Required pill when saved combined doc covers both sides -->
@if (!(existingKycDocuments.length > 0 && !kycDocFiles.back)) {
<span class="kyc-doc-upload-req">Required</span>
}
</div>
<!-- Locked view: saved combined doc covers the back side too. -->
@if (existingKycDocuments.length > 0 && !kycDocFiles.back && readOnly) {
<div class="kyc-doc-dropzone kyc-doc-dropzone--has kyc-doc-dropzone--locked">
<div class="kyc-doc-dropzone__existing">
@if (kycDocMergedPreview) {
<img [src]="kycDocMergedPreview" alt="Saved KYC document" class="kyc-doc-dropzone__thumb" />
} @else {
<mat-icon class="kyc-doc-dropzone__doc-ic">description</mat-icon>
}
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">
{{ existingKycDocuments[0].fileName || existingKycDocuments[0].name }}
</span>
<button type="button" class="kyc-doc-dropzone__view" (click)="viewExistingKycDoc()">
<mat-icon>visibility</mat-icon>
Click here to view
</button>
</div>
</div>
</div>
} @else {
<label
class="kyc-doc-dropzone"
[class.kyc-doc-dropzone--has]="kycDocFiles.back || existingKycDocuments.length > 0"
[class.kyc-doc-dropzone--req]="!kycDocFiles.back && existingKycDocuments.length === 0"
>
<input type="file" accept=".jpg,.jpeg,.png,.pdf" hidden (change)="onKycDocFile($event, 'back')" />
@if (kycDocFiles.back) {
<div class="kyc-doc-dropzone__file">
<mat-icon>insert_drive_file</mat-icon>
<span class="kyc-doc-dropzone__name">{{ kycDocFiles.back.name }}</span>
<button
type="button"
mat-icon-button
class="kyc-doc-dropzone__remove"
(click)="$event.preventDefault(); removeKycDocFile('back')"
>
<mat-icon>close</mat-icon>
</button>
</div>
} @else if (existingKycDocuments.length > 0) {
<!-- Edit mode WITH existing combined doc — view + replace. -->
<div class="kyc-doc-dropzone__existing">
@if (kycDocMergedPreview) {
<img [src]="kycDocMergedPreview" alt="Saved KYC document" class="kyc-doc-dropzone__thumb" />
} @else {
<mat-icon class="kyc-doc-dropzone__doc-ic">description</mat-icon>
}
<div class="kyc-doc-dropzone__existing-meta">
<span class="kyc-doc-dropzone__existing-name">
{{ existingKycDocuments[0].fileName || existingKycDocuments[0].name }}
</span>
<div class="kyc-doc-dropzone__existing-actions">
<button
type="button"
class="kyc-doc-dropzone__view"
(click)="$event.preventDefault(); $event.stopPropagation(); viewExistingKycDoc()"
>
<mat-icon>visibility</mat-icon>
View
</button>
<span class="kyc-doc-dropzone__sep">·</span>
<span class="kyc-doc-dropzone__existing-hint">
<mat-icon>cloud_upload</mat-icon>
Click here to replace
</span>
</div>
</div>
</div>
} @else {
<div class="kyc-doc-dropzone__hint">
<mat-icon>cloud_upload</mat-icon>
<span>Click to upload (JPG / PNG / PDF · max 5 MB)</span>
</div>
}
</label>
}
</div>
}
</div>
<!-- Combined preview + re-crop is an edit-mode tool — hide in read-only,
and don't duplicate the thumbnail when nothing's been picked yet. -->
@if (
selectedKycDoc.sides === 2 && kycDocMergedPreview && !readOnly && (kycDocFiles.front || kycDocFiles.back)
) {
<div class="kyc-merged">
<div class="kyc-merged__head">
<span class="kyc-merged__title">
<mat-icon>filter_none</mat-icon>
Combined preview (front + back)
</span>
<span class="kyc-merged__hint">Saved as a single PNG. Re-crop a side to update the merged file.</span>
</div>
<div class="kyc-merged__preview">
<img [src]="kycDocMergedPreview" alt="Merged KYC document" />
</div>
<div class="kyc-merged__actions">
<button type="button" mat-stroked-button class="kyc-merged__btn" (click)="reCropKycDocFile('front')">
<mat-icon>crop</mat-icon> Re-crop front
</button>
<button type="button" mat-stroked-button class="kyc-merged__btn" (click)="reCropKycDocFile('back')">
<mat-icon>crop</mat-icon> Re-crop back
</button>
</div>
</div>
}
}
</div>
<div class="form-grid">
<mat-form-field appearance="outline" class="form-field">
<mat-label>{{ selectedKycDoc?.documentKeyLabel || 'Document Number' }}</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>tag</mat-icon></span>
<input
matInput
required
formControlName="kycDocumentNumber"
[placeholder]="selectedKycDoc?.documentKeyPlaceholder || 'As printed on document'"
maxlength="30"
style="text-transform: uppercase"
(input)="
kycForm.get('kycDocumentNumber')?.setValue($any($event.target).value.toUpperCase(), { emitEvent: false })
"
/>
@if (kycForm.get('kycDocumentNumber')?.hasError('required') && kycForm.get('kycDocumentNumber')?.touched) {
<mat-error>Document number is <strong>required</strong></mat-error>
} @else if (
kycForm.get('kycDocumentNumber')?.hasError('pattern') && kycForm.get('kycDocumentNumber')?.touched
) {
<mat-error>Only letters, numbers, hyphens and slashes allowed</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="form-field" (click)="docIssueDatePicker.open()">
<mat-label>Issue Date</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>calendar_today</mat-icon></span>
<input matInput [max]="today" [matDatepicker]="docIssueDatePicker" formControlName="kycDocIssueDate" />
<mat-datepicker-toggle matSuffix [for]="docIssueDatePicker"></mat-datepicker-toggle>
<mat-datepicker #docIssueDatePicker></mat-datepicker>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field" (click)="docExpiryDatePicker.open()">
<mat-label>Expiry Date</mat-label>
<span matPrefix class="field-prefix-icon"><mat-icon>event_busy</mat-icon></span>
<input matInput [matDatepicker]="docExpiryDatePicker" formControlName="kycDocExpiryDate" />
<mat-datepicker-toggle matSuffix [for]="docExpiryDatePicker"></mat-datepicker-toggle>
<mat-datepicker #docExpiryDatePicker></mat-datepicker>
<mat-hint>If applicable (Passport, DL, Visa)</mat-hint>
</mat-form-field>
</div>
</div>
<!-- ═══ SECTION: Risk & KYC Review ═══ -->
<div class="section-block">
<h4 class="section-title">
<fa-icon icon="shield-alt" class="section-icon"></fa-icon>
Risk Category &amp; KYC Review
</h4>
<div class="form-grid">
<div class="checkbox-field override-checkbox-wrapper full-width">
<mat-checkbox formControlName="allowRiskOverride" [disabled]="readOnly">
Override Auto-calculated Risk Category
</mat-checkbox>
</div>
<mat-form-field appearance="outline" class="form-field">
<mat-label>Risk Category</mat-label>
<mat-select formControlName="riskCategory">
<mat-select-trigger>
@if (kycForm.get('riskCategory')?.value === 1) {
<span class="risk-option"><span class="risk-meter risk-low"></span> Low Risk</span>
} @else if (kycForm.get('riskCategory')?.value === 2) {
<span class="risk-option"><span class="risk-meter risk-medium"></span> Medium Risk</span>
} @else if (kycForm.get('riskCategory')?.value === 3) {
<span class="risk-option"><span class="risk-meter risk-high"></span> High Risk</span>
}
</mat-select-trigger>
<mat-option [value]="1">
<span class="risk-option"
><span class="risk-meter risk-low"></span> Low Risk <span class="risk-hint">10 yr renewal</span></span
>
</mat-option>
<mat-option [value]="2">
<span class="risk-option"
><span class="risk-meter risk-medium"></span> Medium Risk
<span class="risk-hint">8 yr renewal</span></span
>
</mat-option>
<mat-option [value]="3">
<span class="risk-option"
><span class="risk-meter risk-high"></span> High Risk <span class="risk-hint">2 yr renewal</span></span
>
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field">
<mat-label>KYC Review Due Date</mat-label>
<input matInput [value]="getKycExpiryDate(getRenewalYears())" disabled />
<mat-hint>Auto: Low 10yr, Medium 8yr, High 2yr</mat-hint>
</mat-form-field>
@if (riskCalculationReason && !kycForm.get('allowRiskOverride')?.value && !readOnly) {
<div
class="risk-auto-calc-info full-width"
[class.risk-auto-calc-info--low]="kycForm.get('riskCategory')?.value === 1"
[class.risk-auto-calc-info--medium]="kycForm.get('riskCategory')?.value === 2"
[class.risk-auto-calc-info--high]="kycForm.get('riskCategory')?.value === 3"
>
<mat-icon class="info-icon">info</mat-icon>
<span class="info-text"
>Auto-determined: <strong>{{ riskCalculationReason }}</strong></span
>
</div>
}
<div class="risk-rules-toggle-wrapper full-width">
<button mat-button type="button" class="rules-toggle-btn" (click)="showRiskRulesTable = !showRiskRulesTable">
<mat-icon class="rules-toggle-icon">{{ showRiskRulesTable ? 'expand_less' : 'expand_more' }}</mat-icon>
{{
showRiskRulesTable ? 'Hide Risk Classification Criteria Table' : 'View Risk Classification Criteria Table'
}}
</button>
</div>
@if (showRiskRulesTable) {
<div class="risk-rules-card mat-elevation-z1 full-width">
<table class="risk-rules-table">
<thead>
<tr>
<th>Risk Category</th>
<th>Primary Criteria / Basis</th>
<th>Examples / Thresholds</th>
</tr>
</thead>
<tbody>
<tr class="rule-row rule-row--high">
<td><span class="risk-badge risk-badge--high">High Risk</span></td>
<td>PEPs, High-Risk Industries, High Income / Turnovers</td>
<td>
Politicians; Real Estate, Import/Export; Commission Agents; Income > ₹20L/yr; Turnover > ₹1 Crore
</td>
</tr>
<tr class="rule-row rule-row--medium">
<td><span class="risk-badge risk-badge--medium">Medium Risk</span></td>
<td>Self-Employed, Professionals, Cross-Border, Moderate Income</td>
<td>
Business Owners, Traders; Doctors, Lawyers, CAs; NRI, PIO, OCI; Income > ₹6L/yr; Turnover > ₹20L
</td>
</tr>
<tr class="rule-row rule-row--low">
<td><span class="risk-badge risk-badge--low">Low Risk</span></td>
<td>Non-Earning, Government Employment, Agriculture / Manual</td>
<td>Students, Minors, Homemakers; Govt/PSU Employees; Farmers, Daily Wage; Income <= ₹6L/yr</td>
</tr>
</tbody>
</table>
</div>
}
<mat-form-field appearance="outline" class="form-field full-width">
<mat-label>{{ isKycVerified ? 'Reason for Re-edit *' : 'Remarks' }}</mat-label>
<textarea
matInput
formControlName="remarks"
rows="2"
[placeholder]="isKycVerified ? 'Mandatory — explain why KYC is being re-edited' : ''"
></textarea>
@if (isKycVerified && !kycForm.get('remarks')?.value?.trim()) {
<mat-error>Remarks are mandatory when updating a verified KYC record</mat-error>
}
</mat-form-field>
@if (isKycVerified) {
<div class="kyc-reedit-alert">
<mat-icon class="kyc-reedit-alert__icon">info</mat-icon>
<span
>KYC is <strong>VERIFIED</strong>. Any update will reset the status to <strong>IN PROGRESS</strong> and
require PAN re-verification.</span
>
</div>
}
</div>
<!-- KYC Expiry Alert Cards -->
<div class="kyc-expiry-section">
<div class="kyc-expiry-header">
<fa-icon icon="bell" class="kyc-expiry-header-icon"></fa-icon>
<span>KYC Expiry Alert</span>
</div>
<div class="kyc-expiry-body">
<div class="kyc-expiry-cards">
@for (rc of riskCards; track rc.value) {
<div class="kyc-expiry-card" [class.active]="kycForm.get('riskCategory')?.value === rc.value">
<span class="risk-gauge" [class]="'risk-gauge--' + rc.color">
<svg viewBox="0 0 40 24" class="risk-gauge-svg">
<path
d="M4 22 A16 16 0 0 1 36 22"
fill="none"
stroke="#e2e8f0"
stroke-width="4"
stroke-linecap="round"
/>
<path
d="M4 22 A16 16 0 0 1 36 22"
fill="none"
[attr.stroke]="rc.color === 'green' ? '#22c55e' : rc.color === 'amber' ? '#f59e0b' : '#ef4444'"
stroke-width="4"
stroke-linecap="round"
[attr.stroke-dasharray]="rc.color === 'green' ? '16 50' : rc.color === 'amber' ? '33 50' : '50 50'"
/>
<line
x1="20"
y1="22"
[attr.x2]="rc.color === 'green' ? '10' : rc.color === 'amber' ? '20' : '30'"
[attr.y2]="rc.color === 'green' ? '10' : rc.color === 'amber' ? '6' : '10'"
stroke="#334155"
stroke-width="2"
stroke-linecap="round"
/>
<circle cx="20" cy="22" r="2" fill="#334155" />
</svg>
</span>
<div class="kyc-expiry-card__info">
<span class="kyc-expiry-card__label">{{ rc.label }}</span>
<span class="kyc-expiry-card__cycle"
>Renewal every <strong>{{ rc.years }} years</strong></span
>
</div>
@if (kycForm.get('riskCategory')?.value === rc.value) {
<span class="kyc-expiry-card__next">Next: {{ getKycExpiryDate(rc.years) }}</span>
}
</div>
}
</div>
@if (kycForm.get('riskCategory')?.value) {
<div
class="kyc-expiry-alert"
[class.kyc-expiry-alert--low]="kycForm.get('riskCategory')?.value === 1"
[class.kyc-expiry-alert--medium]="kycForm.get('riskCategory')?.value === 2"
[class.kyc-expiry-alert--high]="kycForm.get('riskCategory')?.value === 3"
>
<fa-icon icon="exclamation-circle" class="kyc-expiry-alert__icon"></fa-icon>
<span
>System will auto-alert before KYC expiry on <strong>{{ getKycExpiryDate(getRenewalYears()) }}</strong
>.</span
>
</div>
}
</div>
</div>
</div>
<!-- ═══ SECTION: AML & Compliance Checks ═══ -->
<div class="section-block">
<h4 class="section-title aml-title">
<fa-icon icon="exclamation-triangle" class="section-icon"></fa-icon>
AML &amp; Compliance Checks
</h4>
<div class="form-grid">
<div class="checkbox-field override-checkbox-wrapper full-width">
<mat-checkbox formControlName="allowAmlOverride" [disabled]="readOnly">
Override Auto-calculated AML &amp; Compliance Checks
</mat-checkbox>
</div>
<mat-form-field appearance="outline" class="form-field">
<mat-label>AML Check Status</mat-label>
<mat-select required formControlName="amlCheckStatus">
<mat-option value="CLEAR">Clear</mat-option>
<mat-option value="FLAGGED">Flagged</mat-option>
<mat-option value="BLOCKED">Blocked</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field">
<mat-label>Banned Entity List Match</mat-label>
<mat-select required formControlName="bannedEntityMatch">
<mat-option value="NO">No</mat-option>
<mat-option value="YES">Yes</mat-option>
<mat-option value="PARTIAL_MATCH">Partial Match</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field">
<mat-label>Willful Defaulter Flag</mat-label>
<mat-select formControlName="willfulDefaulter">
<mat-option value="NO">No</mat-option>
<mat-option value="YES">Yes</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field">
<mat-label>ECGC SAL Flag</mat-label>
<mat-select formControlName="ecgcSalFlag">
<mat-option value="NO">No</mat-option>
<mat-option value="YES">Yes</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field">
<mat-label>Dormant / Inoperative Flag</mat-label>
<mat-select formControlName="dormantFlag">
<mat-option value="ACTIVE">Active</mat-option>
<mat-option value="DORMANT">Dormant</mat-option>
<mat-option value="INOPERATIVE">Inoperative</mat-option>
</mat-select>
</mat-form-field>
</div>
@if (kycForm.get('amlCheckStatus')?.value === 'BLOCKED' || kycForm.get('bannedEntityMatch')?.value === 'YES') {
<div class="aml-block-alert">
<fa-icon icon="ban" class="aml-block-icon"></fa-icon>
<span><strong>BLOCKED</strong> — Account opening cannot proceed.</span>
</div>
}
</div>
<!-- ═══ SECTION: Freeze / Debit Controls ═══ -->
<div class="section-block">
<h4 class="section-title freeze-title">
<fa-icon icon="lock" class="section-icon"></fa-icon>
Freeze / Debit Controls
</h4>
<div class="form-grid">
<mat-form-field appearance="outline" class="form-field">
<mat-label>Account Status</mat-label>
<mat-select formControlName="accountStatus">
<mat-option value="ACTIVE">Active</mat-option>
<mat-option value="FROZEN">Frozen</mat-option>
<mat-option value="UNFREEZE">Unfreeze</mat-option>
<mat-option value="NO_DEBIT">No Debit</mat-option>
</mat-select>
</mat-form-field>
@if (kycForm.get('accountStatus')?.value === 'FROZEN') {
<mat-form-field appearance="outline" class="form-field">
<mat-label>Reason for Freeze</mat-label>
<mat-select required formControlName="freezeReason">
<mat-option value="COURT_ORDER">Court Order</mat-option>
<mat-option value="AML">AML</mat-option>
<mat-option value="REGULATORY">Regulatory</mat-option>
<mat-option value="INTERNAL">Internal</mat-option>
<mat-option value="OTHER">Other</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field">
<mat-label>Freeze Authority</mat-label>
<mat-select required formControlName="freezeAuthority">
<mat-option value="BRANCH_MANAGER">Branch Manager</mat-option>
<mat-option value="HO">Head Office</mat-option>
<mat-option value="REGULATORY">Regulatory Authority</mat-option>
</mat-select>
</mat-form-field>
}
</div>
</div>
<!-- ═══ SECTION: Form 97/98 ═══ -->
<div class="section-block">
<h4 class="section-title">
<fa-icon icon="file-invoice" class="section-icon"></fa-icon>
Form 97 / 98
@if (!kycForm.get('hasPan')?.value) {
<span class="kyc-badge required-badge">Required — No PAN</span>
}
</h4>
@if (!kycForm.get('hasPan')?.value) {
<div class="kyc-info-hint warning-hint">
<mat-icon class="kyc-info-hint-icon">warning</mat-icon>
<span>Client does not have PAN. Form 97/98 is mandatory.</span>
</div>
<div class="form-grid">
<mat-form-field appearance="outline" class="form-field">
<mat-label>Form Type</mat-label>
<mat-select formControlName="form60Type">
<mat-option value="FORM_60">Form 97</mat-option>
<mat-option value="FORM_61">Form 98</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field" (click)="form60DatePicker.open()">
<mat-label>Declaration Date</mat-label>
<input matInput [matDatepicker]="form60DatePicker" formControlName="form60Date" />
<mat-datepicker-toggle matSuffix [for]="form60DatePicker"></mat-datepicker-toggle>
<mat-datepicker #form60DatePicker></mat-datepicker>
</mat-form-field>
</div>
} @else {
<p class="form60-not-needed">Client has PAN — Form 97/98 not required.</p>
}
</div>
<!-- ═══ SECTION: Form 121 ═══ -->
<div class="section-block">
<h4 class="section-title">
<fa-icon icon="receipt" class="section-icon"></fa-icon>
Form 121
</h4>
<div class="form-grid">
<div class="form-field checkbox-field">
<mat-checkbox formControlName="form15Applicable">Form 121 Applicable</mat-checkbox>
</div>
@if (kycForm.get('form15Applicable')?.value) {
<mat-form-field appearance="outline" class="form-field">
<mat-label>Form Type</mat-label>
<mat-select formControlName="form15Type">
<mat-option value="15G">Form 121 (Below 60 years)</mat-option>
<mat-option value="15H">Form 121 (Senior Citizen 60+)</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline" class="form-field">
<mat-label>Financial Year</mat-label>
<input matInput formControlName="form15FY" placeholder="e.g. 2025-26" />
</mat-form-field>
}
</div>
</div>
<!-- ═══ SECTION: Photograph & Signature ═══ -->
<div class="section-block">
<h4 class="section-title">
<fa-icon icon="camera" class="section-icon"></fa-icon>
Photograph &amp; Signature
<span class="section-required-tag">Required</span>
</h4>
<div class="photo-sig-grid">
<div
class="upload-box"
[class.upload-box--has-preview]="photographPreview"
[class.upload-box--missing]="!photographPreview && photoSigTouched"
>
@if (photographPreview) {
<div class="upload-preview">
<img [src]="photographPreview" alt="Photo" class="upload-preview__img upload-preview__img--photo" />
<div class="upload-preview__actions">
<label class="upload-preview__btn recrop">
<input type="file" accept="image/*" hidden (change)="onPhotoSelected($event)" />
<fa-icon icon="crop"></fa-icon> Re-crop
</label>
<button type="button" class="upload-preview__btn remove" (click)="removePhoto()">
<fa-icon icon="trash"></fa-icon> Remove
</button>
</div>
</div>
} @else {
<div class="upload-box__icon"><fa-icon icon="user"></fa-icon></div>
<span class="upload-box__label">
Client Photograph
<span class="upload-box__req">*</span>
</span>
<span class="upload-box__hint">JPG / PNG, max 2MB</span>
<div class="upload-box__btn-row">
<label class="upload-box__btn">
<input type="file" accept="image/*" hidden (change)="onPhotoSelected($event)" />
<fa-icon icon="camera"></fa-icon> Choose &amp; Crop
</label>
<button type="button" class="upload-box__btn upload-box__btn--secondary" (click)="captureWebcamPhoto()">
<fa-icon icon="video"></fa-icon> Use Webcam
</button>
</div>
@if (photoSigTouched) {
<span class="upload-box__error">Photograph is required</span>
}
}
</div>
<div
class="upload-box"
[class.upload-box--has-preview]="signaturePreview"
[class.upload-box--missing]="!signaturePreview && photoSigTouched"
>
@if (signaturePreview) {
<div class="upload-preview">
<img [src]="signaturePreview" alt="Signature" class="upload-preview__img upload-preview__img--sig" />
<div class="upload-preview__actions">
<label class="upload-preview__btn recrop">
<input type="file" accept="image/*" hidden (change)="onSignatureSelected($event)" />
<fa-icon icon="crop"></fa-icon> Re-crop
</label>
<button type="button" class="upload-preview__btn remove" (click)="removeSignature()">
<fa-icon icon="trash"></fa-icon> Remove
</button>
</div>
</div>
} @else {
<div class="upload-box__icon"><fa-icon icon="pen"></fa-icon></div>
<span class="upload-box__label">
Specimen Signature
<span class="upload-box__req">*</span>
</span>
<span class="upload-box__hint">Scanned image, max 1MB</span>
<div class="upload-box__btn-row">
<label class="upload-box__btn">
<input type="file" accept="image/*" hidden (change)="onSignatureSelected($event)" />
<fa-icon icon="crop"></fa-icon> Choose &amp; Crop
</label>
<button type="button" class="upload-box__btn upload-box__btn--secondary" (click)="drawSignature()">
<fa-icon icon="pen"></fa-icon> Draw Signature
</button>
</div>
@if (photoSigTouched) {
<span class="upload-box__error">Signature is required</span>
}
}
</div>
</div>
</div>
</div>