- Implement DIP/MNRL lookup logs table, filtering, and detail review under reports. - Integrate DIP verification gating on client creation, allowing officers to record PROCEEDED/REJECTED decisions with reasons. - Upgrade Sanctions verification dialog to support comparing Name, DOB, and Nationality particulars. - Add Sanctions disposition recording (FALSE_POSITIVE / CONFIRMED_MATCH) awaiting Principal Officer approval. - Add sanctions-alert-review-dialog for reviewers to assess and escalate/clear flagged names. - Enforce strict screening reset logic on client name changes, preventing bypass of existing blocks. - Update routes and shell menu navigation to include DIP/MNRL Lookups and Sanctions Logs.
1506 lines
68 KiB
HTML
1506 lines
68 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/.
|
||
-->
|
||
|
||
<form [formGroup]="createClientForm">
|
||
<div class="step-header">
|
||
<div class="step-header__left">
|
||
<fa-icon icon="user-plus" class="step-header__icon"></fa-icon>
|
||
<div>
|
||
<h3 class="step-header__title">General Details</h3>
|
||
<p class="step-header__sub">Client registration — personal & identification details.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section: Basic Information -->
|
||
<div class="section-block">
|
||
<h4 class="section-title">
|
||
<fa-icon icon="briefcase" class="section-icon"></fa-icon>
|
||
Basic Information
|
||
</h4>
|
||
<div class="form-grid">
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ 'labels.inputs.Office' | translate }}</mat-label>
|
||
<mat-select required formControlName="officeId">
|
||
@for (office of officeOptions; track office) {
|
||
<mat-option [value]="office.id">{{ office.name }}</mat-option>
|
||
}
|
||
</mat-select>
|
||
@if (createClientForm.controls.officeId.hasError('required')) {
|
||
<mat-error>Office is <strong>required</strong></mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ 'labels.inputs.Legal Form' | translate }}</mat-label>
|
||
<mat-select required formControlName="legalFormId">
|
||
<mat-select-trigger>
|
||
@if (createClientForm.get('legalFormId')?.value) {
|
||
<span class="legal-form-option">
|
||
<mat-icon class="legal-form-option-icon">{{
|
||
createClientForm.get('legalFormId')?.value === 1 ? 'person' : 'business'
|
||
}}</mat-icon>
|
||
{{ getSelectedLegalFormLabel() }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (legalForm of legalFormOptions; track legalForm) {
|
||
<mat-option [value]="legalForm.id">
|
||
<span class="legal-form-option">
|
||
<mat-icon class="legal-form-option-icon">{{ legalForm.id === 1 ? 'person' : 'business' }}</mat-icon>
|
||
{{ legalForm.id === 1 ? 'Individual' : 'Non-Individual' }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Nationality</mat-label>
|
||
<mat-select formControlName="nationalityId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedNationality()) {
|
||
<span class="nat-option">
|
||
@if (getSelectedNationality().flagType === 'india') {
|
||
<span class="nat-flag">
|
||
<svg viewBox="0 0 36 24" class="nat-flag-svg">
|
||
<rect width="36" height="8" fill="#FF9933" />
|
||
<rect y="8" width="36" height="8" fill="#FFFFFF" />
|
||
<rect y="16" width="36" height="8" fill="#138808" />
|
||
<circle cx="18" cy="12" r="3" fill="#000080" />
|
||
<circle cx="18" cy="12" r="2.5" fill="#FFFFFF" />
|
||
<circle cx="18" cy="12" r="0.7" fill="#000080" />
|
||
</svg>
|
||
</span>
|
||
} @else {
|
||
<mat-icon class="nat-globe-icon">{{ getSelectedNationality().icon }}</mat-icon>
|
||
}
|
||
{{ getSelectedNationality().label }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (nat of nationalityOptions; track nat) {
|
||
<mat-option [value]="nat.value">
|
||
<span class="nat-option">
|
||
@if (nat.flagType === 'india') {
|
||
<span class="nat-flag">
|
||
<svg viewBox="0 0 36 24" class="nat-flag-svg">
|
||
<rect width="36" height="8" fill="#FF9933" />
|
||
<rect y="8" width="36" height="8" fill="#FFFFFF" />
|
||
<rect y="16" width="36" height="8" fill="#138808" />
|
||
<circle cx="18" cy="12" r="3" fill="#000080" />
|
||
<circle cx="18" cy="12" r="2.5" fill="#FFFFFF" />
|
||
<circle cx="18" cy="12" r="0.7" fill="#000080" />
|
||
</svg>
|
||
</span>
|
||
} @else {
|
||
<mat-icon class="nat-globe-icon">{{ nat.icon }}</mat-icon>
|
||
}
|
||
{{ nat.label }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.PERSON) {
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ 'labels.inputs.Is staff' | translate }}?</mat-label>
|
||
<mat-select formControlName="isStaff">
|
||
<mat-option [value]="false">No</mat-option>
|
||
<mat-option [value]="true">Yes</mat-option>
|
||
</mat-select>
|
||
</mat-form-field>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section: Client Name & Personal Details -->
|
||
<div class="section-block">
|
||
<h4 class="section-title">
|
||
<fa-icon icon="user" class="section-icon"></fa-icon>
|
||
{{ 'labels.inputs.Client Details' | translate }}
|
||
</h4>
|
||
<div class="form-grid">
|
||
<!-- Entity: Full Name -->
|
||
@if (createClientForm.get('fullname')) {
|
||
<mat-form-field appearance="outline" class="form-field full-width">
|
||
<mat-label>
|
||
{{
|
||
'labels.inputs.' + getDateLabel(createClientForm.get('legalFormId')?.value, ['Name', 'Entity Name'])
|
||
| translate
|
||
}}
|
||
</mat-label>
|
||
<input
|
||
matInput
|
||
required
|
||
formControlName="fullname"
|
||
autocomplete="off"
|
||
(blur)="capitalizeControl('fullname')"
|
||
style="text-transform: capitalize"
|
||
/>
|
||
@if (createClientForm.controls.fullname.hasError('required')) {
|
||
<mat-error>Client name is <strong>required</strong></mat-error>
|
||
} @else if (createClientForm.controls.fullname.hasError('pattern')) {
|
||
<mat-error>Only letters, spaces, dots, hyphens and apostrophes allowed</mat-error>
|
||
} @else if (createClientForm.controls.fullname.hasError('maxlength')) {
|
||
<mat-error>Name cannot exceed 100 characters</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<!-- Entity Name Verify -->
|
||
@if (createClientForm.get('fullname')?.value) {
|
||
<div class="full-name-row full-width">
|
||
<div class="full-name-preview">
|
||
<span class="full-name-label">Entity Name:</span>
|
||
<span class="full-name-value" style="text-transform: capitalize">{{
|
||
createClientForm.get('fullname')?.value
|
||
}}</span>
|
||
</div>
|
||
<div class="verify-inline">
|
||
@if (!sanctionsVerified && (!sanctionsBlocked || sanctionsNeedsRescreen)) {
|
||
<button
|
||
mat-stroked-button
|
||
color="primary"
|
||
type="button"
|
||
class="verify-name-btn"
|
||
[disabled]="readOnly"
|
||
(click)="verifySanctions()"
|
||
>
|
||
<fa-icon icon="shield-alt" class="verify-name-icon"></fa-icon>
|
||
{{ sanctionsNeedsRescreen ? 'Re-verify Entity' : 'Verify Entity' }}
|
||
</button>
|
||
}
|
||
@if (sanctionsVerified) {
|
||
<span class="sanctions-citation">{{ sanctionsCitation }}</span>
|
||
<span class="verify-status verified"> <fa-icon icon="check-circle"></fa-icon> Verified </span>
|
||
<mat-icon
|
||
class="sanctions-info-icon"
|
||
[matTooltip]="sanctionsTooltip"
|
||
matTooltipClass="sanctions-tooltip"
|
||
>info</mat-icon
|
||
>
|
||
}
|
||
@if (sanctionsBlocked) {
|
||
<span class="sanctions-citation">{{ sanctionsCitation }}</span>
|
||
<span class="verify-status blocked">
|
||
<fa-icon icon="exclamation-triangle"></fa-icon> {{ sanctionsStatusLabel }}
|
||
</span>
|
||
<mat-icon
|
||
class="sanctions-info-icon blocked-info"
|
||
[matTooltip]="sanctionsTooltip"
|
||
matTooltipClass="sanctions-tooltip"
|
||
>info</mat-icon
|
||
>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
<!-- Person: Salutation + First / Middle / Last Name -->
|
||
@if (
|
||
createClientForm.get('firstname') || createClientForm.get('middlename') || createClientForm.get('lastname')
|
||
) {
|
||
<div class="name-fields-row">
|
||
@if (createClientForm.get('salutationId') && salutationOptions?.length > 0) {
|
||
<mat-form-field appearance="outline" class="name-field salutation-field-wide">
|
||
<mat-label>Salutation</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>person</mat-icon></span>
|
||
<mat-select formControlName="salutationId">
|
||
@for (salutation of salutationOptions; track salutation) {
|
||
<mat-option [value]="salutation.id">{{ salutation.name }}</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
}
|
||
@if (createClientForm.get('firstname')) {
|
||
<mat-form-field appearance="outline" class="name-field first-name">
|
||
<mat-label>{{ 'labels.inputs.First Name' | translate }}</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>badge</mat-icon></span>
|
||
<input
|
||
matInput
|
||
required
|
||
formControlName="firstname"
|
||
autocomplete="off"
|
||
(blur)="capitalizeControl('firstname')"
|
||
style="text-transform: capitalize"
|
||
/>
|
||
@if (createClientForm.controls.firstname.hasError('required')) {
|
||
<mat-error>First name is <strong>required</strong></mat-error>
|
||
} @else if (createClientForm.controls.firstname.hasError('pattern')) {
|
||
<mat-error>Only letters, spaces, dots and hyphens allowed</mat-error>
|
||
} @else if (createClientForm.controls.firstname.hasError('maxlength')) {
|
||
<mat-error>Cannot exceed 50 characters</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
}
|
||
@if (createClientForm.get('middlename')) {
|
||
<mat-form-field appearance="outline" class="name-field middle-name">
|
||
<mat-label>{{ 'labels.inputs.Middle Name' | translate }}</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>person_outline</mat-icon></span>
|
||
<input
|
||
matInput
|
||
formControlName="middlename"
|
||
autocomplete="off"
|
||
(blur)="capitalizeControl('middlename')"
|
||
style="text-transform: capitalize"
|
||
/>
|
||
@if (
|
||
createClientForm.controls.middlename?.hasError('pattern') &&
|
||
createClientForm.controls.middlename?.touched
|
||
) {
|
||
<mat-error>Only letters, spaces, dots and hyphens allowed</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
}
|
||
@if (createClientForm.get('lastname')) {
|
||
<mat-form-field appearance="outline" class="name-field last-name">
|
||
<mat-label>{{ 'labels.inputs.Last Name' | translate }}</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>person</mat-icon></span>
|
||
<input
|
||
matInput
|
||
required
|
||
formControlName="lastname"
|
||
autocomplete="off"
|
||
(blur)="capitalizeControl('lastname')"
|
||
style="text-transform: capitalize"
|
||
/>
|
||
@if (createClientForm.controls.lastname.hasError('required')) {
|
||
<mat-error>Last name is <strong>required</strong></mat-error>
|
||
} @else if (createClientForm.controls.lastname.hasError('pattern')) {
|
||
<mat-error>Only letters, spaces, dots and hyphens allowed</mat-error>
|
||
} @else if (createClientForm.controls.lastname.hasError('maxlength')) {
|
||
<mat-error>Cannot exceed 50 characters</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
}
|
||
</div>
|
||
|
||
<!-- Full Name Preview + Verify -->
|
||
@if (fullNamePreview) {
|
||
<div class="full-name-row">
|
||
<div class="full-name-preview">
|
||
<span class="full-name-label">Full Name:</span>
|
||
<span class="full-name-value">{{ fullNamePreview }}</span>
|
||
</div>
|
||
<div class="verify-inline">
|
||
@if (!sanctionsVerified && (!sanctionsBlocked || sanctionsNeedsRescreen)) {
|
||
<button
|
||
mat-stroked-button
|
||
color="primary"
|
||
type="button"
|
||
class="verify-name-btn"
|
||
[disabled]="readOnly"
|
||
(click)="verifySanctions()"
|
||
>
|
||
<fa-icon icon="shield-alt" class="verify-name-icon"></fa-icon>
|
||
{{ sanctionsNeedsRescreen ? 'Re-verify Client' : 'Verify Client' }}
|
||
</button>
|
||
}
|
||
@if (sanctionsVerified) {
|
||
<span class="sanctions-citation">{{ sanctionsCitation }}</span>
|
||
<span class="verify-status verified"> <fa-icon icon="check-circle"></fa-icon> Verified </span>
|
||
<mat-icon
|
||
class="sanctions-info-icon"
|
||
[matTooltip]="sanctionsTooltip"
|
||
matTooltipClass="sanctions-tooltip"
|
||
>info</mat-icon
|
||
>
|
||
}
|
||
@if (sanctionsBlocked) {
|
||
<span class="sanctions-citation">{{ sanctionsCitation }}</span>
|
||
<span class="verify-status blocked">
|
||
<fa-icon icon="exclamation-triangle"></fa-icon> {{ sanctionsStatusLabel }}
|
||
</span>
|
||
<mat-icon
|
||
class="sanctions-info-icon blocked-info"
|
||
[matTooltip]="sanctionsTooltip"
|
||
matTooltipClass="sanctions-tooltip"
|
||
>info</mat-icon
|
||
>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
<!-- DOB (Person only) -->
|
||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.PERSON) {
|
||
<mat-form-field appearance="outline" class="form-field" (click)="dateOfBirthDatePicker.open()">
|
||
<mat-label>Date of Birth</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>calendar_today</mat-icon></span>
|
||
<input
|
||
matInput
|
||
required
|
||
[max]="maxDate"
|
||
[matDatepicker]="dateOfBirthDatePicker"
|
||
formControlName="dateOfBirth"
|
||
/>
|
||
<mat-datepicker-toggle matSuffix [for]="dateOfBirthDatePicker"></mat-datepicker-toggle>
|
||
<mat-datepicker #dateOfBirthDatePicker></mat-datepicker>
|
||
@if (
|
||
createClientForm.get('dateOfBirth')?.hasError('required') && createClientForm.get('dateOfBirth')?.touched
|
||
) {
|
||
<mat-error>Date of Birth is <strong>required</strong></mat-error>
|
||
}
|
||
</mat-form-field>
|
||
}
|
||
|
||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.PERSON) {
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ 'labels.inputs.Gender' | translate }}</mat-label>
|
||
<mat-select required formControlName="genderId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedGender()) {
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{ getGenderIcon(getSelectedGender().name) }}</mat-icon>
|
||
{{ getSelectedGender().name | titlecase }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (gender of genderOptions; track gender) {
|
||
<mat-option [value]="gender.id">
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{ getGenderIcon(gender.name) }}</mat-icon>
|
||
{{ gender.name | titlecase }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
@if (createClientForm.get('genderId')?.hasError('required') && createClientForm.get('genderId')?.touched) {
|
||
<mat-error>Gender is <strong>required</strong></mat-error>
|
||
}
|
||
</mat-form-field>
|
||
}
|
||
|
||
<!-- Age display below DOB row -->
|
||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.PERSON && calculatedAgeDetail) {
|
||
<div class="age-inline full-width" [class.age-minor]="isMinor">
|
||
<fa-icon [icon]="isMinor ? 'child' : 'clock'" class="age-inline-icon"></fa-icon>
|
||
<span>{{ calculatedAgeDetail }}</span>
|
||
@if (isMinor) {
|
||
<span class="minor-badge">Minor</span>
|
||
}
|
||
@if (isSeniorCitizen) {
|
||
<span class="senior-citizen-badge">Senior Citizen</span>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<!-- Minor / Guardian Mapping (mandatory if age < 18) -->
|
||
@if (isMinor && createClientForm.get('legalFormId')?.value === LegalFormId.PERSON) {
|
||
<div class="guardian-section">
|
||
<!-- Header bar -->
|
||
<div class="guardian-section__header">
|
||
<div class="guardian-section__header-left">
|
||
<mat-icon class="guardian-section__header-icon">family_restroom</mat-icon>
|
||
<div>
|
||
<h4 class="guardian-section__title">Guardian Details</h4>
|
||
<span class="guardian-section__sub">Minor account — guardian is mandatory per RBI KYC norms</span>
|
||
</div>
|
||
</div>
|
||
<div class="guardian-section__header-right">
|
||
<div class="guardian-section__age-chips">
|
||
<span class="guardian-age-chip"
|
||
><strong>{{ ageBreakdown.years }}</strong> yrs</span
|
||
>
|
||
<span class="guardian-age-chip"
|
||
><strong>{{ ageBreakdown.months }}</strong> mos</span
|
||
>
|
||
<span class="guardian-age-chip"
|
||
><strong>{{ ageBreakdown.days }}</strong> days</span
|
||
>
|
||
<span class="guardian-minor-badge">MINOR</span>
|
||
</div>
|
||
@if (!selectedGuardian && !guardianLinking) {
|
||
<button mat-flat-button type="button" class="guardian-add-btn" (click)="guardianLinking = true">
|
||
<mat-icon>person_add</mat-icon>
|
||
Link Guardian
|
||
</button>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Body -->
|
||
<div class="guardian-section__body" formGroupName="guardianDetails">
|
||
<!-- Empty state: no guardian linked, not linking -->
|
||
@if (!selectedGuardian && !guardianLinking) {
|
||
<div class="guardian-empty guardian-empty--required">
|
||
<mat-icon class="guardian-empty__icon">person_off</mat-icon>
|
||
<div class="guardian-empty__content">
|
||
<span class="guardian-empty__text"
|
||
>No guardian linked yet. Click <strong>"Link Guardian"</strong> to search and add.</span
|
||
>
|
||
<span class="guardian-empty__required">Guardian is mandatory for minor accounts</span>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- Linking mode: search + relationship -->
|
||
@if (!selectedGuardian && guardianLinking) {
|
||
<div class="guardian-search-row">
|
||
<mat-form-field appearance="outline" class="guardian-rel-field">
|
||
<mat-label>Relationship</mat-label>
|
||
<mat-select required formControlName="guardianRelation">
|
||
<mat-option value="FATHER"><mat-icon class="icon-option-icon">man</mat-icon> Father</mat-option>
|
||
<mat-option value="MOTHER"><mat-icon class="icon-option-icon">woman</mat-icon> Mother</mat-option>
|
||
<mat-option value="LEGAL_GUARDIAN"
|
||
><mat-icon class="icon-option-icon">gavel</mat-icon> Legal Guardian</mat-option
|
||
>
|
||
<mat-option value="GRANDPARENT"
|
||
><mat-icon class="icon-option-icon">elderly</mat-icon> Grandparent</mat-option
|
||
>
|
||
<mat-option value="OTHER"><mat-icon class="icon-option-icon">group</mat-icon> Other</mat-option>
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<div class="guardian-search-wrapper guardian-search-col">
|
||
<mat-form-field
|
||
appearance="outline"
|
||
class="guardian-search-field"
|
||
[class.guardian-search-disabled]="!createClientForm.get('guardianDetails.guardianRelation')?.value"
|
||
>
|
||
<mat-label>Search Guardian by Name or Client ID</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>person_search</mat-icon></span>
|
||
<input
|
||
matInput
|
||
[value]="guardianSearchText"
|
||
(input)="onGuardianSearch($any($event.target).value)"
|
||
(blur)="dismissGuardianSearch()"
|
||
[disabled]="!createClientForm.get('guardianDetails.guardianRelation')?.value"
|
||
placeholder="Select relationship first, then search..."
|
||
autocomplete="off"
|
||
/>
|
||
</mat-form-field>
|
||
|
||
@if (guardianSearching) {
|
||
<div class="guardian-results">
|
||
<div class="guardian-results__loading">
|
||
<mat-progress-spinner diameter="20" mode="indeterminate"></mat-progress-spinner>
|
||
<span>Searching...</span>
|
||
</div>
|
||
</div>
|
||
}
|
||
@if (!guardianSearching && guardianSearchResults.length > 0) {
|
||
<div class="guardian-results">
|
||
@for (result of guardianSearchResults; track result.id) {
|
||
<div class="guardian-result-item" (mousedown)="selectGuardian(result); $event.preventDefault()">
|
||
<div class="guardian-result-item__avatar">
|
||
<fa-icon icon="user"></fa-icon>
|
||
</div>
|
||
<div class="guardian-result-item__info">
|
||
<span class="guardian-result-item__name">{{ result.displayName }}</span>
|
||
<span class="guardian-result-item__meta">
|
||
ID: {{ result.id }} · A/C: {{ result.accountNumber }} ·
|
||
{{ result.officeName }}
|
||
@if (result.dateOfBirth) {
|
||
· DOB: {{ result.dateOfBirth | dateFormat }}
|
||
}
|
||
@if (result.fatherSpouseName) {
|
||
· {{ result.fatherSpouseName }}
|
||
}
|
||
</span>
|
||
</div>
|
||
<span class="guardian-result-item__status" [class.active]="result.status?.value === 'Active'">
|
||
{{ result.status?.value }}
|
||
</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
@if (!guardianSearching && guardianSearchText.length >= 2 && guardianSearchResults.length === 0) {
|
||
<div class="search-no-results-hint">No clients found</div>
|
||
}
|
||
</div>
|
||
|
||
<button
|
||
mat-icon-button
|
||
type="button"
|
||
class="guardian-cancel-link"
|
||
(click)="guardianLinking = false"
|
||
matTooltip="Cancel"
|
||
>
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
</div>
|
||
}
|
||
|
||
<!-- Linked Guardian: compact row -->
|
||
@if (selectedGuardian) {
|
||
<div class="guardian-row-table">
|
||
<!-- Table header -->
|
||
<div class="guardian-row__header">
|
||
<span class="guardian-row__col guardian-row__col--photo">Photo</span>
|
||
<span class="guardian-row__col guardian-row__col--name">Name</span>
|
||
<span class="guardian-row__col guardian-row__col--rel">Relationship</span>
|
||
<span class="guardian-row__col guardian-row__col--cif">CIF / A/C</span>
|
||
<span class="guardian-row__col guardian-row__col--status">Status</span>
|
||
<span class="guardian-row__col guardian-row__col--actions">Actions</span>
|
||
</div>
|
||
|
||
<!-- Data row -->
|
||
<div class="guardian-row__data">
|
||
<span class="guardian-row__col guardian-row__col--photo">
|
||
<div class="guardian-row__avatar" [class.has-photo]="selectedGuardian.profileImageUrl">
|
||
@if (selectedGuardian.profileImageUrl) {
|
||
<img
|
||
[src]="selectedGuardian.profileImageUrl"
|
||
alt="Photo"
|
||
class="avatar-photo guardian-row__zoomable"
|
||
(click)="zoomGuardianImage(selectedGuardian.profileImageUrl, 'Guardian Photo')"
|
||
/>
|
||
} @else {
|
||
<fa-icon icon="user-shield"></fa-icon>
|
||
}
|
||
</div>
|
||
</span>
|
||
<span class="guardian-row__col guardian-row__col--name">
|
||
<div class="guardian-row__name-info">
|
||
<span class="guardian-row__name-text">{{ selectedGuardian.displayName }}</span>
|
||
<span class="guardian-row__linked-tag">
|
||
<mat-icon class="guardian-row__link-icon">link</mat-icon>
|
||
Linked Client #{{ selectedGuardian.cifNumber || selectedGuardian.id }}
|
||
</span>
|
||
</div>
|
||
</span>
|
||
<span class="guardian-row__col guardian-row__col--rel">
|
||
<span class="guardian-row__rel-badge">{{
|
||
createClientForm.get('guardianDetails.guardianRelation')?.value || '-'
|
||
}}</span>
|
||
</span>
|
||
<span class="guardian-row__col guardian-row__col--cif">
|
||
<span class="guardian-row__mono">{{
|
||
selectedGuardian.accountNo || selectedGuardian.accountNumber || '-'
|
||
}}</span>
|
||
</span>
|
||
<span class="guardian-row__col guardian-row__col--status">
|
||
<span
|
||
class="guardian-row__status-badge"
|
||
[class.guardian-row__status-badge--active]="selectedGuardian.status?.value === 'Active'"
|
||
>
|
||
{{ selectedGuardian.status?.value || '-' }}
|
||
</span>
|
||
</span>
|
||
<span class="guardian-row__col guardian-row__col--actions">
|
||
<button
|
||
mat-icon-button
|
||
type="button"
|
||
class="guardian-action-btn"
|
||
(click)="guardianExpanded = !guardianExpanded"
|
||
[matTooltip]="guardianExpanded ? 'Collapse' : 'View Details'"
|
||
>
|
||
<mat-icon>{{ guardianExpanded ? 'expand_less' : 'visibility' }}</mat-icon>
|
||
</button>
|
||
<button
|
||
mat-icon-button
|
||
type="button"
|
||
class="guardian-action-btn guardian-action-btn--delete"
|
||
(click)="removeGuardian()"
|
||
matTooltip="Delete"
|
||
>
|
||
<mat-icon>delete</mat-icon>
|
||
</button>
|
||
</span>
|
||
</div>
|
||
|
||
<!-- Expandable detail panel -->
|
||
@if (guardianExpanded) {
|
||
<div class="guardian-row__expand">
|
||
<div class="guardian-expand__grid">
|
||
<div class="guardian-expand__item">
|
||
<span class="guardian-expand__label">Account Type</span>
|
||
<span class="guardian-expand__value">{{ selectedGuardian.accountType || '-' }}</span>
|
||
</div>
|
||
@if (selectedGuardian.panNumber) {
|
||
<div class="guardian-expand__item">
|
||
<span class="guardian-expand__label">PAN</span>
|
||
<span class="guardian-expand__value"
|
||
>{{ selectedGuardian.panNumber }}
|
||
@if (selectedGuardian.panVerified) {
|
||
<mat-icon class="verify-tick-sm">check_circle</mat-icon>
|
||
}
|
||
</span>
|
||
</div>
|
||
}
|
||
@if (selectedGuardian.aadhaarMasked) {
|
||
<div class="guardian-expand__item">
|
||
<span class="guardian-expand__label">Aadhaar</span>
|
||
<span class="guardian-expand__value"
|
||
>{{ selectedGuardian.aadhaarMasked }}
|
||
@if (selectedGuardian.aadhaarVerified) {
|
||
<mat-icon class="verify-tick-sm">check_circle</mat-icon>
|
||
}
|
||
</span>
|
||
</div>
|
||
}
|
||
@if (selectedGuardian.mobileNo) {
|
||
<div class="guardian-expand__item">
|
||
<span class="guardian-expand__label">Mobile</span>
|
||
<span class="guardian-expand__value">{{ selectedGuardian.mobileNo }}</span>
|
||
</div>
|
||
}
|
||
@if (selectedGuardian.fullAddress) {
|
||
<div class="guardian-expand__item guardian-expand__item--wide">
|
||
<span class="guardian-expand__label">Address</span>
|
||
<span class="guardian-expand__value">{{ selectedGuardian.fullAddress }}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
@if (selectedGuardian.signatureUrl) {
|
||
<div class="guardian-expand__sig">
|
||
<span class="guardian-expand__label">Specimen Signature</span>
|
||
<img
|
||
[src]="selectedGuardian.signatureUrl"
|
||
alt="Signature"
|
||
class="guardian-expand__sig-img guardian-row__zoomable"
|
||
(click)="zoomGuardianImage(selectedGuardian.signatureUrl, 'Specimen Signature')"
|
||
/>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- Non-Individual Details -->
|
||
@if (createClientForm.get('clientNonPersonDetails')) {
|
||
<div class="form-grid" formGroupName="clientNonPersonDetails" style="margin-top: 16px">
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Constitution Type</mat-label>
|
||
<mat-select formControlName="constitutionId" required>
|
||
<mat-select-trigger>
|
||
@if (selectedConstitution) {
|
||
<mat-icon class="constitution-icon">{{ selectedConstitution.icon }}</mat-icon>
|
||
{{ selectedConstitution.label }}
|
||
}
|
||
</mat-select-trigger>
|
||
@for (ct of enrichedConstitutionOptions; track ct.id) {
|
||
<mat-option [value]="ct.id">
|
||
<mat-icon class="constitution-icon">{{ ct.icon }}</mat-icon>
|
||
{{ ct.label }}
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Nature of Business / Activity</mat-label>
|
||
<mat-select formControlName="mainBusinessLineId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedBusiness()) {
|
||
<span class="biz-option">
|
||
<mat-icon class="biz-option-icon">{{ getBusinessIcon(getSelectedBusiness().name) }}</mat-icon>
|
||
{{ getSelectedBusiness().name }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (business of businessLineOptions; track business) {
|
||
<mat-option [value]="business.id">
|
||
<span class="biz-option">
|
||
<mat-icon class="biz-option-icon">{{ getBusinessIcon(business.name) }}</mat-icon>
|
||
{{ business.name }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Registration / Incorporation Number</mat-label>
|
||
<input matInput formControlName="incorpNumber" placeholder="{{ selectedConstitution?.regHint || '' }}" />
|
||
@if (selectedConstitution?.regHint) {
|
||
<mat-hint>e.g. {{ selectedConstitution.regHint }}</mat-hint>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<!-- Date of Incorporation is captured on the KYC step, next to the entity PAN it is
|
||
verified against — see client-kyc-step's `incorporationDate` control. -->
|
||
|
||
<!-- Authorized Signatories (multiple, registered clients) -->
|
||
<div class="form-field full-width signatory-section">
|
||
<h4 class="section-title signatory-title">
|
||
<fa-icon icon="user-shield" class="section-icon"></fa-icon>
|
||
Authorized Signatories
|
||
<span class="sig-count-badge">{{ signatories.length }}</span>
|
||
</h4>
|
||
<div class="minor-info-bar">
|
||
<fa-icon icon="info-circle" class="minor-info-icon"></fa-icon>
|
||
<span
|
||
>Select registered clients as authorized signatories — as per Board Resolution / Partnership Deed. Max
|
||
10.</span
|
||
>
|
||
</div>
|
||
|
||
<!-- Signatory List Table -->
|
||
@if (signatories.length > 0) {
|
||
<div class="sig-table">
|
||
<div class="sig-table__header">
|
||
<span class="sig-table__col sig-table__col--sno">#</span>
|
||
<span class="sig-table__col sig-table__col--photo"></span>
|
||
<span class="sig-table__col sig-table__col--name">Name</span>
|
||
<span class="sig-table__col sig-table__col--id">Client ID</span>
|
||
<span class="sig-table__col sig-table__col--pan">PAN</span>
|
||
<span class="sig-table__col sig-table__col--din">DIN</span>
|
||
<span class="sig-table__col sig-table__col--status">Status</span>
|
||
<span class="sig-table__col sig-table__col--actions">Actions</span>
|
||
</div>
|
||
@for (sig of signatories; track sig.id; let i = $index) {
|
||
<div class="sig-table__row">
|
||
<span class="sig-table__col sig-table__col--sno">{{ i + 1 }}</span>
|
||
<span class="sig-table__col sig-table__col--photo">
|
||
<div class="sig-table__avatar" [class.has-photo]="sig.profileImageUrl">
|
||
@if (sig.profileImageUrl) {
|
||
<img [src]="sig.profileImageUrl" alt="" class="avatar-photo" />
|
||
} @else {
|
||
<fa-icon icon="user"></fa-icon>
|
||
}
|
||
</div>
|
||
</span>
|
||
<span class="sig-table__col sig-table__col--name">
|
||
<strong>{{ sig.displayName }}</strong>
|
||
@if (sig._loading) {
|
||
<mat-progress-spinner
|
||
diameter="14"
|
||
mode="indeterminate"
|
||
class="sig-inline-spinner"
|
||
></mat-progress-spinner>
|
||
}
|
||
</span>
|
||
<span class="sig-table__col sig-table__col--id">{{ sig.cifNumber || sig.id }}</span>
|
||
<span class="sig-table__col sig-table__col--pan">
|
||
{{ sig.panNumber || '-' }}
|
||
@if (sig.panVerified) {
|
||
<mat-icon class="verify-tick-sm">check_circle</mat-icon>
|
||
}
|
||
</span>
|
||
<span class="sig-table__col sig-table__col--din">
|
||
@if (sig.dinNumber) {
|
||
{{ sig.dinNumber }}
|
||
@if (sig.dinVerified) {
|
||
<mat-icon class="verify-tick-sm">check_circle</mat-icon>
|
||
}
|
||
@if (!sig.dinVerified) {
|
||
<mat-icon class="verify-cross-sm">cancel</mat-icon>
|
||
}
|
||
} @else {
|
||
<button
|
||
mat-stroked-button
|
||
class="din-verify-btn"
|
||
type="button"
|
||
matTooltip="Verify DIN"
|
||
(click)="openDinVerifyDialog(sig)"
|
||
>
|
||
<mat-icon class="din-verify-icon">badge</mat-icon> Verify DIN
|
||
</button>
|
||
}
|
||
</span>
|
||
<span class="sig-table__col sig-table__col--status">
|
||
<span class="sig-status-chip" [class.active]="sig.status?.value === 'Active'">{{
|
||
sig.status?.value || '-'
|
||
}}</span>
|
||
</span>
|
||
<span class="sig-table__col sig-table__col--actions">
|
||
<button mat-icon-button type="button" matTooltip="View Details" (click)="viewSignatoryDetails(sig)">
|
||
<mat-icon class="sig-action-icon">visibility</mat-icon>
|
||
</button>
|
||
<button mat-icon-button type="button" matTooltip="Remove" (click)="removeSignatoryAt(i)">
|
||
<mat-icon class="sig-action-icon remove">close</mat-icon>
|
||
</button>
|
||
</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<!-- Add Signatory Search -->
|
||
@if (signatories.length < 10) {
|
||
<div class="sig-add-wrapper">
|
||
<div class="guardian-search-wrapper">
|
||
<mat-form-field appearance="outline" class="guardian-search-field">
|
||
<mat-label>Add Signatory — Search by Name or Client ID</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>person_add</mat-icon></span>
|
||
<input
|
||
matInput
|
||
[value]="signatorySearchText"
|
||
(input)="onSignatorySearch($any($event.target).value)"
|
||
(blur)="dismissSignatorySearch()"
|
||
placeholder="Type name or client ID..."
|
||
autocomplete="off"
|
||
/>
|
||
</mat-form-field>
|
||
@if (signatorySearching) {
|
||
<div class="guardian-results">
|
||
<div class="guardian-results__loading">
|
||
<mat-progress-spinner diameter="20" mode="indeterminate"></mat-progress-spinner>
|
||
<span>Searching...</span>
|
||
</div>
|
||
</div>
|
||
}
|
||
@if (!signatorySearching && signatorySearchResults.length > 0) {
|
||
<div class="guardian-results">
|
||
@for (result of signatorySearchResults; track result.id) {
|
||
<div class="guardian-result-item" (mousedown)="addSignatory(result); $event.preventDefault()">
|
||
<div class="guardian-result-item__avatar"><fa-icon icon="user"></fa-icon></div>
|
||
<div class="guardian-result-item__info">
|
||
<span class="guardian-result-item__name">{{ result.displayName }}</span>
|
||
<span class="guardian-result-item__meta"
|
||
>ID: {{ result.id }} · A/C: {{ result.accountNumber }} ·
|
||
{{ result.officeName }}
|
||
@if (result.dateOfBirth) {
|
||
· DOB: {{ result.dateOfBirth | dateFormat }}
|
||
}
|
||
@if (result.fatherSpouseName) {
|
||
· {{ result.fatherSpouseName }}
|
||
}
|
||
</span>
|
||
</div>
|
||
<span class="guardian-result-item__status" [class.active]="result.status?.value === 'Active'">{{
|
||
result.status?.value
|
||
}}</span>
|
||
</div>
|
||
}
|
||
</div>
|
||
}
|
||
@if (!signatorySearching && signatorySearchText.length >= 2 && signatorySearchResults.length === 0) {
|
||
<div class="search-no-results-hint">No clients found</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<!-- Additional field based on constitution -->
|
||
@if (selectedConstitution?.additionalField) {
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ selectedConstitution.additionalField }}</mat-label>
|
||
<input matInput formControlName="additionalInfo" />
|
||
</mat-form-field>
|
||
}
|
||
|
||
<mat-form-field appearance="outline" class="form-field full-width">
|
||
<mat-label>Remarks</mat-label>
|
||
<textarea matInput formControlName="remarks" cdkTextareaAutosize cdkAutosizeMinRows="2"></textarea>
|
||
</mat-form-field>
|
||
</div>
|
||
}
|
||
</div>
|
||
|
||
<!-- Section: Contact Details -->
|
||
<div class="section-block">
|
||
<h4 class="section-title">
|
||
<fa-icon icon="mobile-alt" class="section-icon"></fa-icon>
|
||
Contact Details
|
||
<mat-icon
|
||
class="dip-info-icon"
|
||
matTooltip="Digital Intelligence Platform (DIP) refers to the fraud-monitoring and intelligence ecosystem created by the Department of Telecommunications (DoT)"
|
||
matTooltipPosition="above"
|
||
>info</mat-icon
|
||
>
|
||
</h4>
|
||
|
||
<div class="form-grid">
|
||
<!-- Row 1: Mobile No | DIP Verify (primary) -->
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ 'labels.inputs.Mobile No' | translate }}</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>phone_android</mat-icon></span>
|
||
<input
|
||
matInput
|
||
required
|
||
type="text"
|
||
formControlName="mobileNo"
|
||
placeholder="e.g. 9876543210"
|
||
maxlength="10"
|
||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||
(paste)="$event.preventDefault()"
|
||
/>
|
||
@if (createClientForm.get('mobileNo')?.hasError('required') && createClientForm.get('mobileNo')?.touched) {
|
||
<mat-error>Mobile number is <strong>required</strong></mat-error>
|
||
} @else if (
|
||
createClientForm.get('mobileNo')?.hasError('pattern') && createClientForm.get('mobileNo')?.touched
|
||
) {
|
||
<mat-error>Enter valid 10-digit number (starts with 6-9)</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<div class="form-field mobile-verify-row">
|
||
@if (!mobileVerified) {
|
||
<button
|
||
mat-stroked-button
|
||
color="primary"
|
||
type="button"
|
||
class="verify-btn"
|
||
[disabled]="readOnly || createClientForm.get('mobileNo')?.value?.length !== 10"
|
||
(click)="verifyWithDIP()"
|
||
>
|
||
<fa-icon icon="shield-alt" class="m-r-10"></fa-icon>
|
||
Verify with DIP
|
||
</button>
|
||
} @else {
|
||
<span class="text-success verified-label">✔ Verified</span>
|
||
}
|
||
</div>
|
||
|
||
<!-- Row 2: Alternate Mobile No | Alternate DIP Verify -->
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Alternate Mobile No</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>phone_android</mat-icon></span>
|
||
<input
|
||
matInput
|
||
type="text"
|
||
formControlName="alternateMobileNo"
|
||
placeholder="e.g. 9876543210"
|
||
maxlength="10"
|
||
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
|
||
(paste)="$event.preventDefault()"
|
||
/>
|
||
@if (
|
||
createClientForm.get('alternateMobileNo')?.hasError('pattern') &&
|
||
createClientForm.get('alternateMobileNo')?.touched
|
||
) {
|
||
<mat-error>Enter valid 10-digit number (starts with 6-9)</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<div class="form-field mobile-verify-row">
|
||
@if (!alternateMobileVerified) {
|
||
<button
|
||
mat-stroked-button
|
||
color="primary"
|
||
type="button"
|
||
class="verify-btn"
|
||
[disabled]="readOnly || createClientForm.get('alternateMobileNo')?.value?.length !== 10"
|
||
(click)="verifyAlternateDIP()"
|
||
>
|
||
<fa-icon icon="shield-alt" class="m-r-10"></fa-icon>
|
||
Verify with DIP
|
||
</button>
|
||
} @else {
|
||
<span class="text-success verified-label">✔ Verified</span>
|
||
}
|
||
</div>
|
||
|
||
<!-- Row 3: Email | Marital Status -->
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ 'labels.inputs.Email Address' | translate }}</mat-label>
|
||
<span matPrefix class="field-prefix-icon"><mat-icon>email</mat-icon></span>
|
||
<input matInput required formControlName="emailAddress" placeholder="e.g. name@example.com" />
|
||
@if (
|
||
createClientForm.get('emailAddress')?.hasError('required') && createClientForm.get('emailAddress')?.touched
|
||
) {
|
||
<mat-error>Email is <strong>required</strong></mat-error>
|
||
} @else if (createClientForm.get('emailAddress')?.invalid && createClientForm.get('emailAddress')?.touched) {
|
||
<mat-error>Enter a valid email (e.g. name@example.com)</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
@if (
|
||
createClientForm.get('legalFormId')?.value === LegalFormId.PERSON && maritalStatusOptions.length > 0 && !isMinor
|
||
) {
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Marital Status</mat-label>
|
||
<mat-select formControlName="maritalStatusId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedMaritalStatus()) {
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{
|
||
getMaritalStatusIcon(getSelectedMaritalStatus().name)
|
||
}}</mat-icon>
|
||
{{ getSelectedMaritalStatus().name }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (ms of maritalStatusOptions; track ms) {
|
||
<mat-option [value]="ms.id">
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{ getMaritalStatusIcon(ms.name) }}</mat-icon>
|
||
{{ ms.name }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
}
|
||
|
||
<!-- CLIENT CREATION contact-OTP verification — create mode only. Edit mode already has
|
||
its own dual old/new OTP panel in EditClientComponent's template; this single
|
||
"new value" block only makes sense for a brand-new client (no "old" destination to
|
||
verify against). Each block only appears once its own field passes validation, so
|
||
staff enter a plausible-looking number/email before being asked to verify it. -->
|
||
@if (!clientData) {
|
||
<div class="contact-otp-row">
|
||
@if (createClientForm.get('mobileNo')?.valid) {
|
||
<mifosx-client-contact-otp-block
|
||
channel="MOBILE"
|
||
[destination]="createClientForm.get('mobileNo')?.value"
|
||
label="Verify Mobile Number"
|
||
(stateChange)="onMobileOtpState($event)"
|
||
></mifosx-client-contact-otp-block>
|
||
}
|
||
@if (createClientForm.get('emailAddress')?.valid) {
|
||
<mifosx-client-contact-otp-block
|
||
channel="EMAIL"
|
||
[destination]="createClientForm.get('emailAddress')?.value"
|
||
label="Verify Email Address"
|
||
(stateChange)="onEmailOtpState($event)"
|
||
></mifosx-client-contact-otp-block>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Section: Education & Social Details (Individual only) -->
|
||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.PERSON) {
|
||
<div class="section-block">
|
||
<h4 class="section-title">
|
||
<fa-icon icon="book" class="section-icon"></fa-icon>
|
||
Education & Social Details
|
||
</h4>
|
||
<div class="form-grid">
|
||
<!-- Education Level -->
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Education Level</mat-label>
|
||
<mat-select formControlName="educationLevelId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedEducationLevel()) {
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{
|
||
getEducationLevelIcon(getSelectedEducationLevel().name)
|
||
}}</mat-icon>
|
||
{{ getSelectedEducationLevel().name }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (el of educationLevelOptions; track el) {
|
||
<mat-option [value]="el.id">
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{ getEducationLevelIcon(el.name) }}</mat-icon>
|
||
{{ el.name }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<!-- Degree / Qualification (depends on Education Level) -->
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Degree / Qualification</mat-label>
|
||
<mat-select
|
||
formControlName="degreeQualificationId"
|
||
[placeholder]="getSelectedEducationLevel() ? 'Select Degree' : 'Select Education Level first'"
|
||
>
|
||
<mat-select-trigger>
|
||
@if (getSelectedDegreeQualification()) {
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{
|
||
getDegreeQualificationIcon(getSelectedDegreeQualification().name)
|
||
}}</mat-icon>
|
||
{{ getSelectedDegreeQualification().name }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (dq of filteredDegreeQualificationOptions; track dq) {
|
||
<mat-option [value]="dq.id">
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{ getDegreeQualificationIcon(dq.name) }}</mat-icon>
|
||
{{ dq.name }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
@if (!getSelectedEducationLevel()) {
|
||
<mat-option disabled>
|
||
<em>Please select Education Level first</em>
|
||
</mat-option>
|
||
} @else if (filteredDegreeQualificationOptions.length === 0) {
|
||
<mat-option disabled>
|
||
<em>No options available</em>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<!-- Religion -->
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Religion</mat-label>
|
||
<mat-select formControlName="religionId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedReligion()) {
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{ getReligionIcon(getSelectedReligion().name) }}</mat-icon>
|
||
{{ getSelectedReligion().name }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (r of religionOptions; track r) {
|
||
<mat-option [value]="r.id">
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{ getReligionIcon(r.name) }}</mat-icon>
|
||
{{ r.name }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<!-- Social Category -->
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Social Category</mat-label>
|
||
<mat-select formControlName="socialCategoryId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedSocialCategory()) {
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{
|
||
getSocialCategoryIcon(getSelectedSocialCategory().name)
|
||
}}</mat-icon>
|
||
{{ getSelectedSocialCategory().name }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (sc of socialCategoryOptions; track sc) {
|
||
<mat-option [value]="sc.id">
|
||
<span class="icon-option">
|
||
<mat-icon class="icon-option-icon">{{ getSocialCategoryIcon(sc.name) }}</mat-icon>
|
||
{{ sc.name }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!--
|
||
Section: Turnover & Risk (Non-Individual only)
|
||
|
||
An entity has no salary to declare, so the individual Financial & Classification section below
|
||
never applied to it — which left a company with no turnover on file at all, and its risk stuck
|
||
on the flat entity default. Turnover is the size signal the branch actually has at onboarding,
|
||
so it is captured here and the risk band it produces is shown right beside it: the officer sees
|
||
the grade move as they type, instead of first meeting it on the KYC tab.
|
||
-->
|
||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.ENTITY) {
|
||
<div class="section-block">
|
||
<h4 class="section-title">
|
||
<fa-icon icon="chart-line" class="section-icon"></fa-icon>
|
||
Turnover & Risk Profile
|
||
</h4>
|
||
<div class="form-grid">
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Monthly Turnover</mat-label>
|
||
<span matPrefix class="rupee-prefix">₹</span>
|
||
<input matInput type="number" formControlName="monthlyTurnover" min="0" />
|
||
<mat-hint>Yearly turnover fills in automatically (× 12)</mat-hint>
|
||
@if (
|
||
createClientForm.get('monthlyTurnover')?.hasError('pattern') &&
|
||
createClientForm.get('monthlyTurnover')?.touched
|
||
) {
|
||
<mat-error>Enter a valid amount</mat-error>
|
||
} @else if (
|
||
createClientForm.get('monthlyTurnover')?.hasError('min') && createClientForm.get('monthlyTurnover')?.touched
|
||
) {
|
||
<mat-error>Amount cannot be negative</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Annual Turnover</mat-label>
|
||
<span matPrefix class="rupee-prefix">₹</span>
|
||
<input matInput type="number" formControlName="annualTurnover" min="0" />
|
||
<mat-hint>Drives the risk grade below</mat-hint>
|
||
@if (
|
||
createClientForm.get('annualTurnover')?.hasError('pattern') &&
|
||
createClientForm.get('annualTurnover')?.touched
|
||
) {
|
||
<mat-error>Enter a valid amount</mat-error>
|
||
} @else if (
|
||
createClientForm.get('annualTurnover')?.hasError('min') && createClientForm.get('annualTurnover')?.touched
|
||
) {
|
||
<mat-error>Amount cannot be negative</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<div
|
||
class="full-width entity-risk-card"
|
||
[class.entity-risk-card--low]="entityTurnoverRisk.category === 1"
|
||
[class.entity-risk-card--medium]="entityTurnoverRisk.category === 2"
|
||
[class.entity-risk-card--high]="entityTurnoverRisk.category === 3"
|
||
>
|
||
<div class="entity-risk-card__head">
|
||
<fa-icon icon="shield-alt" class="entity-risk-card__icon"></fa-icon>
|
||
<span class="entity-risk-card__title">Auto-calculated Risk Category</span>
|
||
<span class="entity-risk-card__badge">{{ riskCategoryLabel(entityTurnoverRisk.category) }}</span>
|
||
</div>
|
||
<p class="entity-risk-card__reason">{{ entityTurnoverRisk.reason }}</p>
|
||
<p class="entity-risk-card__scale">
|
||
Up to ₹1 crore — Low · ₹1 crore to ₹10 crore — Medium · Above ₹10 crore — High
|
||
</p>
|
||
<p class="entity-risk-card__note">
|
||
Carried into the KYC tab, where it can still be overridden with a recorded reason.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- Section: Financial & Classification (Individual only) -->
|
||
@if (createClientForm.get('legalFormId')?.value === LegalFormId.PERSON) {
|
||
<div class="section-block">
|
||
<h4 class="section-title">
|
||
<fa-icon icon="chart-line" class="section-icon"></fa-icon>
|
||
Financial & Classification
|
||
</h4>
|
||
<div class="form-grid">
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ 'labels.inputs.Occupation Category' | translate }}</mat-label>
|
||
<mat-select formControlName="occupationCategoryId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedOccupationCategory()) {
|
||
<span class="occ-option">
|
||
<mat-icon class="occ-option-icon">{{
|
||
getOccupationCategoryIcon(getSelectedOccupationCategory().name)
|
||
}}</mat-icon>
|
||
{{ getSelectedOccupationCategory().name }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (cat of occupationCategoryOptions; track cat) {
|
||
<mat-option [value]="cat.id">
|
||
<span class="occ-option">
|
||
<mat-icon class="occ-option-icon">{{ getOccupationCategoryIcon(cat.name) }}</mat-icon>
|
||
{{ cat.name }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>{{ 'labels.inputs.Occupation' | translate }}</mat-label>
|
||
<mat-select formControlName="occupationId">
|
||
<mat-select-trigger>
|
||
@if (getSelectedOccupation()) {
|
||
<span class="occ-option">
|
||
<mat-icon class="occ-option-icon">{{ getOccupationIcon(getSelectedOccupation().name) }}</mat-icon>
|
||
{{ getSelectedOccupation().name }}
|
||
</span>
|
||
}
|
||
</mat-select-trigger>
|
||
@for (occ of occupationOptions; track occ) {
|
||
<mat-option [value]="occ.id">
|
||
<span class="occ-option">
|
||
<mat-icon class="occ-option-icon">{{ getOccupationIcon(occ.name) }}</mat-icon>
|
||
{{ occ.name }}
|
||
</span>
|
||
</mat-option>
|
||
}
|
||
</mat-select>
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Monthly Income</mat-label>
|
||
<span matPrefix class="rupee-prefix">₹</span>
|
||
<input matInput type="number" formControlName="monthlyIncome" min="0" />
|
||
@if (
|
||
createClientForm.get('monthlyIncome')?.hasError('pattern') && createClientForm.get('monthlyIncome')?.touched
|
||
) {
|
||
<mat-error>Enter a valid amount</mat-error>
|
||
} @else if (
|
||
createClientForm.get('monthlyIncome')?.hasError('min') && createClientForm.get('monthlyIncome')?.touched
|
||
) {
|
||
<mat-error>Amount cannot be negative</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Annual Income</mat-label>
|
||
<span matPrefix class="rupee-prefix">₹</span>
|
||
<input matInput type="number" formControlName="annualIncome" min="0" />
|
||
@if (
|
||
createClientForm.get('annualIncome')?.hasError('pattern') && createClientForm.get('annualIncome')?.touched
|
||
) {
|
||
<mat-error>Enter a valid amount</mat-error>
|
||
} @else if (
|
||
createClientForm.get('annualIncome')?.hasError('min') && createClientForm.get('annualIncome')?.touched
|
||
) {
|
||
<mat-error>Amount cannot be negative</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<div class="full-width subsection-title">
|
||
<fa-icon icon="coins" class="subsection-icon"></fa-icon>
|
||
Other Income
|
||
</div>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Agriculture Income</mat-label>
|
||
<span matPrefix class="rupee-prefix">₹</span>
|
||
<input matInput type="number" formControlName="agricultureIncome" min="0" />
|
||
@if (
|
||
createClientForm.get('agricultureIncome')?.hasError('pattern') &&
|
||
createClientForm.get('agricultureIncome')?.touched
|
||
) {
|
||
<mat-error>Enter a valid amount</mat-error>
|
||
} @else if (
|
||
createClientForm.get('agricultureIncome')?.hasError('min') &&
|
||
createClientForm.get('agricultureIncome')?.touched
|
||
) {
|
||
<mat-error>Amount cannot be negative</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Non-Agriculture Income</mat-label>
|
||
<span matPrefix class="rupee-prefix">₹</span>
|
||
<input matInput type="number" formControlName="nonAgricultureIncome" min="0" />
|
||
@if (
|
||
createClientForm.get('nonAgricultureIncome')?.hasError('pattern') &&
|
||
createClientForm.get('nonAgricultureIncome')?.touched
|
||
) {
|
||
<mat-error>Enter a valid amount</mat-error>
|
||
} @else if (
|
||
createClientForm.get('nonAgricultureIncome')?.hasError('min') &&
|
||
createClientForm.get('nonAgricultureIncome')?.touched
|
||
) {
|
||
<mat-error>Amount cannot be negative</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Monthly Turnover</mat-label>
|
||
<span matPrefix class="rupee-prefix">₹</span>
|
||
<input matInput type="number" formControlName="monthlyTurnover" min="0" />
|
||
@if (
|
||
createClientForm.get('monthlyTurnover')?.hasError('pattern') &&
|
||
createClientForm.get('monthlyTurnover')?.touched
|
||
) {
|
||
<mat-error>Enter a valid amount</mat-error>
|
||
} @else if (
|
||
createClientForm.get('monthlyTurnover')?.hasError('min') && createClientForm.get('monthlyTurnover')?.touched
|
||
) {
|
||
<mat-error>Amount cannot be negative</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Yearly Turnover</mat-label>
|
||
<span matPrefix class="rupee-prefix">₹</span>
|
||
<input matInput type="number" formControlName="annualTurnover" min="0" />
|
||
@if (
|
||
createClientForm.get('annualTurnover')?.hasError('pattern') &&
|
||
createClientForm.get('annualTurnover')?.touched
|
||
) {
|
||
<mat-error>Enter a valid amount</mat-error>
|
||
} @else if (
|
||
createClientForm.get('annualTurnover')?.hasError('min') && createClientForm.get('annualTurnover')?.touched
|
||
) {
|
||
<mat-error>Amount cannot be negative</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<div class="full-width subsection-title">
|
||
<fa-icon icon="user-shield" class="subsection-icon"></fa-icon>
|
||
Senior Citizen / Person with Disability
|
||
</div>
|
||
|
||
@if (isSeniorCitizen) {
|
||
<div class="full-width senior-citizen-auto-note">
|
||
<fa-icon icon="check-circle"></fa-icon>
|
||
Senior Citizen (60+ years) — detected automatically from Date of Birth
|
||
</div>
|
||
|
||
<div class="full-width doc-upload-field">
|
||
<label class="doc-upload-label">
|
||
<input type="file" accept=".jpg,.jpeg,.png,.pdf" hidden (change)="onSeniorCitizenProofSelected($event)" />
|
||
<fa-icon icon="file-upload"></fa-icon>
|
||
@if (seniorCitizenProofFile) {
|
||
<span>{{ seniorCitizenProofFile.name }}</span>
|
||
} @else if (existingSeniorCitizenProofDoc) {
|
||
<span>{{ existingSeniorCitizenProofDoc.name }} (uploaded)</span>
|
||
} @else {
|
||
<span>Upload Senior Citizen Proof (Age / ID Proof) — optional</span>
|
||
}
|
||
</label>
|
||
@if (seniorCitizenProofFile) {
|
||
<button type="button" mat-icon-button (click)="removeSeniorCitizenProof()" matTooltip="Remove">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
} @else if (existingSeniorCitizenProofDoc) {
|
||
<button type="button" mat-icon-button (click)="viewExistingSeniorCitizenProof()" matTooltip="View">
|
||
<mat-icon>visibility</mat-icon>
|
||
</button>
|
||
<button type="button" mat-icon-button (click)="removeExistingSeniorCitizenProof()" matTooltip="Remove">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
}
|
||
</div>
|
||
}
|
||
|
||
<div class="checkbox-field full-width">
|
||
<mat-checkbox formControlName="isPwd">Person with Disability (PWD)</mat-checkbox>
|
||
</div>
|
||
|
||
@if (createClientForm.get('isPwd')?.value) {
|
||
<mat-form-field appearance="outline" class="form-field">
|
||
<mat-label>Disability Type</mat-label>
|
||
<mat-select formControlName="pwdCategoryId">
|
||
@for (category of pwdCategoryOptions; track category.id) {
|
||
<mat-option [value]="category.id">{{ category.name }}</mat-option>
|
||
}
|
||
</mat-select>
|
||
@if (seniorPwdDocsTouched && !createClientForm.get('pwdCategoryId')?.value) {
|
||
<mat-error>Disability type is required</mat-error>
|
||
}
|
||
</mat-form-field>
|
||
|
||
<div class="full-width doc-upload-field">
|
||
<label
|
||
class="doc-upload-label"
|
||
[class.doc-upload-label--missing]="
|
||
seniorPwdDocsTouched && !pwdCertificateFile && !existingPwdCertificateDoc
|
||
"
|
||
>
|
||
<input type="file" accept=".jpg,.jpeg,.png,.pdf" hidden (change)="onPwdCertificateSelected($event)" />
|
||
<fa-icon icon="file-upload"></fa-icon>
|
||
@if (pwdCertificateFile) {
|
||
<span>{{ pwdCertificateFile.name }}</span>
|
||
} @else if (existingPwdCertificateDoc) {
|
||
<span>{{ existingPwdCertificateDoc.name }} (uploaded)</span>
|
||
} @else {
|
||
<span>Upload Disability Certificate <span class="doc-upload-req">*</span></span>
|
||
}
|
||
</label>
|
||
@if (pwdCertificateFile) {
|
||
<button type="button" mat-icon-button (click)="removePwdCertificate()" matTooltip="Remove">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
} @else if (existingPwdCertificateDoc) {
|
||
<button type="button" mat-icon-button (click)="viewExistingPwdCertificate()" matTooltip="View">
|
||
<mat-icon>visibility</mat-icon>
|
||
</button>
|
||
<button type="button" mat-icon-button (click)="removeExistingPwdCertificate()" matTooltip="Remove">
|
||
<mat-icon>close</mat-icon>
|
||
</button>
|
||
}
|
||
@if (seniorPwdDocsTouched && !pwdCertificateFile && !existingPwdCertificateDoc) {
|
||
<span class="doc-upload-error">Required when PWD is checked</span>
|
||
}
|
||
</div>
|
||
}
|
||
</div>
|
||
</div>
|
||
}
|
||
|
||
<!-- submittedOnDate is set automatically via form default (business date) -->
|
||
</form>
|