Merge remote-tracking branch 'origin/uate' into uate
This commit is contained in:
+86
-1
@@ -516,6 +516,51 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets transient session state back to a blank-client baseline.
|
||||
*
|
||||
* This app's RouteReusableStrategy (core/route/route-reusable-strategy.ts) keeps the SAME
|
||||
* component instance alive across repeat visits to /clients/create — `ngOnInit`/the
|
||||
* constructor never re-run for a second "create new client" session. Without this, computed
|
||||
* state like `isMinor`, the linked guardian, or a prior sanctions/OTP verification would leak
|
||||
* from whatever client was created in the previous session into the new one. Called by
|
||||
* CreateClientComponent whenever `route.data` re-emits (which happens on every navigation,
|
||||
* even a reused one).
|
||||
*/
|
||||
resetForNewClient(): void {
|
||||
this.createClientForm.reset();
|
||||
// legalFormId's raw FormControl default is '' — the buildDependencies() subscription
|
||||
// treats any non-PERSON value (including '') as "non-individual" and adds fullname /
|
||||
// clientNonPersonDetails. Patch it back to the same PERSON default buildDependencies()
|
||||
// itself applies on first construction, so a reset lands on a blank Individual client
|
||||
// instead of stuck with leftover Entity-only controls.
|
||||
this.createClientForm.get('legalFormId')?.setValue(LegalFormId.PERSON);
|
||||
this.isMinor = false;
|
||||
this.calculatedAgeDetail = null;
|
||||
this.ageBreakdown = { years: 0, months: 0, days: 0 };
|
||||
this.selectedGuardian = null;
|
||||
this.guardianSearchText = '';
|
||||
this.guardianSearchResults = [];
|
||||
this.guardianExpanded = false;
|
||||
if (this.createClientForm.get('guardianDetails')) {
|
||||
this.createClientForm.removeControl('guardianDetails');
|
||||
}
|
||||
// Verification state must not carry over — a previous client's cleared sanctions
|
||||
// screening or verified OTP must not silently apply to a different new client.
|
||||
this.mobileVerified = false;
|
||||
this.dipLookupLogId = null;
|
||||
this.dipTimestamp = null;
|
||||
this.alternateMobileVerified = false;
|
||||
this.alternateDipLookupLogId = null;
|
||||
this.alternateDipTimestamp = null;
|
||||
this.mobileNewOtpId = null;
|
||||
this.emailNewOtpId = null;
|
||||
this.sanctionsVerified = false;
|
||||
this.sanctionsBlocked = false;
|
||||
this.screeningLogId = null;
|
||||
this.verifiedName = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-fills the General step form from a manually-entered family member
|
||||
* when the user clicks "Create as Client" on the family members tab.
|
||||
@@ -571,7 +616,11 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
mobileNo: data.mobileNo,
|
||||
alternateMobileNo: data.alternateMobileNo || '',
|
||||
emailAddress: data.emailAddress,
|
||||
dateOfBirth: data.dateOfBirth ? new Date(data.dateOfBirth) : '',
|
||||
dateOfBirth: data.dateOfBirth
|
||||
? Array.isArray(data.dateOfBirth)
|
||||
? new Date(data.dateOfBirth[0], data.dateOfBirth[1] - 1, data.dateOfBirth[2])
|
||||
: new Date(data.dateOfBirth)
|
||||
: '',
|
||||
occupationCategoryId: data.occupationCategory?.id || data.occupationCategoryId || '',
|
||||
occupationId: data.occupation?.id || data.occupationId || '',
|
||||
monthlyIncome: data.monthlyIncome || '',
|
||||
@@ -653,6 +702,42 @@ export class ClientGeneralStepComponent implements OnInit, OnDestroy, OnChanges
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Restore linked guardian for minor clients. The `guardianDetails` control group above was
|
||||
// just created synchronously by the dateOfBirth subscription (patched a few lines up), which
|
||||
// also resets `selectedGuardian` to null — so this has to run after that patch, not before it.
|
||||
// If a guardian is linked in the database (or the client is marked minor), we force minor state
|
||||
// and explicitly ensure the form control exists so the guardian details load correctly.
|
||||
if ((data.isMinor || this.isMinor || data.guardianClientId) && data.guardianClientId) {
|
||||
this.isMinor = true;
|
||||
if (!this.createClientForm.get('guardianDetails')) {
|
||||
this.createClientForm.addControl(
|
||||
'guardianDetails',
|
||||
this.formBuilder.group({
|
||||
guardianClientId: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
guardianRelation: [
|
||||
'',
|
||||
Validators.required
|
||||
]
|
||||
})
|
||||
);
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.createClientForm.get('guardianDetails')?.patchValue({
|
||||
guardianClientId: data.guardianClientId,
|
||||
guardianRelation: data.guardianRelation || ''
|
||||
});
|
||||
this.selectedGuardian = {
|
||||
id: data.guardianClientId,
|
||||
displayName: data.guardianClientDisplayName || '',
|
||||
_loading: true
|
||||
};
|
||||
this.fetchClientFullDetails(data.guardianClientId, this.selectedGuardian);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+32
-1
@@ -43,6 +43,9 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
@Input() clientId: string = '';
|
||||
/** Read-only mode — disables form editing until user clicks edit icon */
|
||||
@Input() readOnly = false;
|
||||
/** Minor clients can't hold utility bills, bank statements or an adult-only OVD in their own
|
||||
* name — restricts the address-proof picker to Aadhaar/Passport when true. */
|
||||
@Input() isMinor = false;
|
||||
|
||||
activeTab = 0;
|
||||
sameAsPermanent = false;
|
||||
@@ -240,7 +243,9 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
this.clientsService.getKycProofTypes().subscribe({
|
||||
next: (res: any) => {
|
||||
const activeFilter = (p: any) => p?.active !== false;
|
||||
this.addressProofs = (res?.addressProofs || []).filter(activeFilter).map(normalizeProofType);
|
||||
this.addressProofs = this.filterAddressProofsForMinor(
|
||||
(res?.addressProofs || []).filter(activeFilter).map(normalizeProofType)
|
||||
);
|
||||
this.addressGroups = groupProofsByCategory(this.addressProofs);
|
||||
this.loadingProofs = false;
|
||||
},
|
||||
@@ -250,6 +255,21 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
});
|
||||
}
|
||||
|
||||
/** Minors don't hold utility bills, bank statements, rent agreements, property tax receipts,
|
||||
* employer letters or pension orders in their own name, and Voter ID/Driving Licence both
|
||||
* require adult age — Aadhaar and Passport are the only OVDs left for proof of address. */
|
||||
private static readonly MINOR_ELIGIBLE_ADDRESS_PROOF_CODES = new Set([
|
||||
'AADHAAR',
|
||||
'PASSPORT'
|
||||
]);
|
||||
|
||||
private filterAddressProofsForMinor(proofs: ProofType[]): ProofType[] {
|
||||
if (!this.isMinor) return proofs;
|
||||
return proofs.filter((p) =>
|
||||
ClientIndiaAddressStepComponent.MINOR_ELIGIBLE_ADDRESS_PROOF_CODES.has((p.code || '').toUpperCase())
|
||||
);
|
||||
}
|
||||
|
||||
selectAddrProof(proof: ProofType): void {
|
||||
this.selectedAddrProof = proof;
|
||||
this.permanentForm.get('proofId')?.setValue(proof.id);
|
||||
@@ -355,6 +375,17 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changes['isMinor'] && !changes['isMinor'].firstChange && this.addressProofs.length) {
|
||||
const filtered = this.filterAddressProofsForMinor(this.addressProofs);
|
||||
if (filtered.length !== this.addressProofs.length) {
|
||||
this.addressProofs = filtered;
|
||||
this.addressGroups = groupProofsByCategory(this.addressProofs);
|
||||
if (this.selectedAddrProof && !filtered.some((p) => p.id === this.selectedAddrProof?.id)) {
|
||||
this.selectedAddrProof = null;
|
||||
this.permanentForm.get('proofId')?.setValue('');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,33 +28,36 @@
|
||||
<div class="form-grid">
|
||||
<!-- ══ INDIVIDUAL KYC ══ -->
|
||||
@if (!isNonIndividual) {
|
||||
@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>
|
||||
}
|
||||
<!-- 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>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="form-field checkbox-field full-width">
|
||||
<mat-checkbox formControlName="hasPan">Client has PAN Card</mat-checkbox>
|
||||
</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 (kycForm.get('hasPan')?.value) {
|
||||
@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>
|
||||
@@ -1266,11 +1269,11 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ SECTION: Form 60/61 ═══ -->
|
||||
<!-- ═══ SECTION: Form 97/98 ═══ -->
|
||||
<div class="section-block">
|
||||
<h4 class="section-title">
|
||||
<fa-icon icon="file-invoice" class="section-icon"></fa-icon>
|
||||
Form 60 / 61
|
||||
Form 97 / 98
|
||||
@if (!kycForm.get('hasPan')?.value) {
|
||||
<span class="kyc-badge required-badge">Required — No PAN</span>
|
||||
}
|
||||
@@ -1278,14 +1281,14 @@
|
||||
@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 60/61 is mandatory.</span>
|
||||
<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 60</mat-option>
|
||||
<mat-option value="FORM_61">Form 61</mat-option>
|
||||
<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()">
|
||||
@@ -1296,26 +1299,26 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
} @else {
|
||||
<p class="form60-not-needed">Client has PAN — Form 60/61 not required.</p>
|
||||
<p class="form60-not-needed">Client has PAN — Form 97/98 not required.</p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- ═══ SECTION: Form 15G / 15H ═══ -->
|
||||
<!-- ═══ SECTION: Form 121 ═══ -->
|
||||
<div class="section-block">
|
||||
<h4 class="section-title">
|
||||
<fa-icon icon="receipt" class="section-icon"></fa-icon>
|
||||
Form 15G / 15H
|
||||
Form 121
|
||||
</h4>
|
||||
<div class="form-grid">
|
||||
<div class="form-field checkbox-field">
|
||||
<mat-checkbox formControlName="form15Applicable">Form 15G/15H Applicable</mat-checkbox>
|
||||
<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">15G (Below 60 years)</mat-option>
|
||||
<mat-option value="15H">15H (Senior Citizen 60+)</mat-option>
|
||||
<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">
|
||||
|
||||
@@ -61,6 +61,8 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
/** Set by parent after client is created */
|
||||
@Input() clientId: string = '';
|
||||
@Input() isNonIndividual = false;
|
||||
/** Minor clients don't hold a PAN card — hides the PAN section on the individual KYC form */
|
||||
@Input() isMinor = false;
|
||||
/** Constitution metadata (panLabel, showCin, etc.) passed from general step */
|
||||
@Input() constitutionMeta: any = null;
|
||||
/** Entity full name (non-individual) — used for enterprise PAN verification */
|
||||
@@ -163,7 +165,9 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
{ name: 'Driving Licence', code: 'DRIVING_LICENSE' },
|
||||
{ name: 'Voter ID', code: 'VOTER_ID' },
|
||||
{ name: 'NREGA Job Card', code: 'NREGA_JOB_CARD' },
|
||||
{ name: 'National Population Register Letter', code: 'NPR_LETTER' }
|
||||
{ name: 'National Population Register Letter', code: 'NPR_LETTER' },
|
||||
{ name: 'Birth Certificate', code: 'BIRTH_CERTIFICATE' },
|
||||
{ name: 'School ID Card', code: 'SCHOOL_ID_CARD' }
|
||||
];
|
||||
/** Constitution-based KYC document types for non-individual clients */
|
||||
private constitutionDocTypes: Record<string, { name: string; mandatory: boolean }[]> = {
|
||||
@@ -458,15 +462,44 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
form15FY: ['']
|
||||
});
|
||||
|
||||
/** RBI OVDs a minor cannot hold: Voter ID and Driving Licence require adult age,
|
||||
* PAN and NREGA job cards are issued to adults. Aadhaar, Passport and NPR letters
|
||||
* are unaffected by age and stay available. */
|
||||
private static readonly MINOR_INELIGIBLE_DOC_KEYWORDS = [
|
||||
'pan',
|
||||
'driving',
|
||||
'voter',
|
||||
'nrega'
|
||||
];
|
||||
|
||||
/** Birth Certificate / School ID Card are minor-specific OVDs — not relevant once the
|
||||
* client is an adult, so they're hidden the other direction. */
|
||||
private static readonly MINOR_ONLY_DOC_CODES = new Set([
|
||||
'BIRTH_CERTIFICATE',
|
||||
'SCHOOL_ID_CARD'
|
||||
]);
|
||||
|
||||
private filterDocTypesForMinor<T extends { name: string; code?: string }>(docs: T[]): T[] {
|
||||
if (this.isMinor) {
|
||||
return docs.filter((d) => {
|
||||
const text = `${d.name || ''} ${d.code || ''}`.toLowerCase();
|
||||
return !ClientKycStepComponent.MINOR_INELIGIBLE_DOC_KEYWORDS.some((k) => text.includes(k));
|
||||
});
|
||||
}
|
||||
return docs.filter((d) => !ClientKycStepComponent.MINOR_ONLY_DOC_CODES.has((d.code || '').toUpperCase()));
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Seed defaults immediately so the picker has options before clientId is set
|
||||
// (template API requires clientId; user can still pick a type pre-save).
|
||||
if (!this.kycDocumentTypes.length) {
|
||||
this.kycDocumentTypes = this.defaultDocumentTypes.map((d) => ({
|
||||
name: d.name,
|
||||
code: d.code,
|
||||
mandatory: false
|
||||
}));
|
||||
this.kycDocumentTypes = this.filterDocTypesForMinor(
|
||||
this.defaultDocumentTypes.map((d) => ({
|
||||
name: d.name,
|
||||
code: d.code,
|
||||
mandatory: false
|
||||
}))
|
||||
);
|
||||
this.rebuildKycDocPicker();
|
||||
}
|
||||
this.loadExistingKycIfNeeded();
|
||||
@@ -496,6 +529,21 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
if (changes['constitutionMeta'] && this.isNonIndividual) {
|
||||
this.updateDocTypesForConstitution();
|
||||
}
|
||||
if (changes['isMinor']) {
|
||||
if (this.isMinor) {
|
||||
this.kycForm.get('hasPan')?.setValue(false);
|
||||
this.kycForm.get('panNumber')?.setValue('');
|
||||
}
|
||||
const filtered = this.filterDocTypesForMinor(this.kycDocumentTypes);
|
||||
if (filtered.length !== this.kycDocumentTypes.length) {
|
||||
this.kycDocumentTypes = filtered;
|
||||
const currentName = this.kycForm.get('kycDocumentName')?.value;
|
||||
if (currentName && !filtered.some((d) => d.name === currentName)) {
|
||||
this.kycForm.get('kycDocumentName')?.setValue('');
|
||||
}
|
||||
this.rebuildKycDocPicker();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Update KYC document types based on selected constitution type */
|
||||
@@ -879,11 +927,13 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.kycTemplate = template;
|
||||
this.kycTemplateLoaded = true;
|
||||
if (template?.requiredDocuments?.length > 0) {
|
||||
this.kycDocumentTypes = template.requiredDocuments.map((doc: any) => ({
|
||||
name: doc.name,
|
||||
code: doc.code || doc.name,
|
||||
mandatory: doc.mandatory ?? false
|
||||
}));
|
||||
this.kycDocumentTypes = this.filterDocTypesForMinor(
|
||||
template.requiredDocuments.map((doc: any) => ({
|
||||
name: doc.name,
|
||||
code: doc.code || doc.name,
|
||||
mandatory: doc.mandatory ?? false
|
||||
}))
|
||||
);
|
||||
// Update kycDocumentName validator based on mandatory docs
|
||||
const hasMandatory = this.kycDocumentTypes.some((d) => d.mandatory);
|
||||
if (hasMandatory) {
|
||||
@@ -896,11 +946,13 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
|
||||
error: () => {
|
||||
// Fallback to defaults if template API unavailable
|
||||
this.kycTemplateLoaded = true;
|
||||
this.kycDocumentTypes = this.defaultDocumentTypes.map((d) => ({
|
||||
name: d.name,
|
||||
code: d.code,
|
||||
mandatory: false
|
||||
}));
|
||||
this.kycDocumentTypes = this.filterDocTypesForMinor(
|
||||
this.defaultDocumentTypes.map((d) => ({
|
||||
name: d.name,
|
||||
code: d.code,
|
||||
mandatory: false
|
||||
}))
|
||||
);
|
||||
this.rebuildKycDocPicker();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -191,7 +191,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'form60',
|
||||
component: Form60TabComponent,
|
||||
data: { title: 'Form 60', breadcrumb: 'Form 60', routeParamBreadcrumb: false }
|
||||
data: { title: 'Form 97', breadcrumb: 'Form 97', routeParamBreadcrumb: false }
|
||||
},
|
||||
{
|
||||
path: 'documents',
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
<div class="f60-pan-ok__icon-wrap">
|
||||
<mat-icon class="f60-pan-ok__icon">verified</mat-icon>
|
||||
</div>
|
||||
<h3 class="f60-pan-ok__title">Form 60 Not Required</h3>
|
||||
<h3 class="f60-pan-ok__title">Form 97 Not Required</h3>
|
||||
<p class="f60-pan-ok__desc">
|
||||
PAN <strong>{{ panNumber }}</strong> is attached to this client.
|
||||
@if (panVerified) {
|
||||
<mat-icon class="f60-pan-ok__tick">check_circle</mat-icon> <span class="f60-pan-ok__verified">Verified</span>
|
||||
}
|
||||
</p>
|
||||
<p class="f60-pan-ok__rule">As per Income Tax Rule 114B, Form 60 is only required when PAN is not available.</p>
|
||||
<p class="f60-pan-ok__rule">As per Income Tax Rule 114B, Form 97 is only required when PAN is not available.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<div class="f60-warning">
|
||||
<mat-icon class="f60-warning__icon">warning_amber</mat-icon>
|
||||
<div class="f60-warning__text">
|
||||
<strong>PAN not provided</strong> — Form 60 declaration is mandatory as per Income Tax Rule 114B.
|
||||
<strong>PAN not provided</strong> — Form 97 declaration is mandatory as per Income Tax Rule 114B.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<div class="f60-card">
|
||||
<div class="f60-card__header">
|
||||
<mat-icon class="f60-card__header-icon">description</mat-icon>
|
||||
<h3>Form 60 Declaration</h3>
|
||||
<h3>Form 97 Declaration</h3>
|
||||
<span class="f60-card__status">Submitted</span>
|
||||
<button mat-stroked-button color="primary" (click)="toggleEditMode()" class="f60-card__edit">
|
||||
<mat-icon>edit</mat-icon> Edit
|
||||
@@ -123,7 +123,7 @@
|
||||
<div class="f60-card">
|
||||
<div class="f60-card__header">
|
||||
<mat-icon class="f60-card__header-icon">edit_note</mat-icon>
|
||||
<h3>Form 60 Declaration</h3>
|
||||
<h3>Form 97 Declaration</h3>
|
||||
</div>
|
||||
<div class="f60-card__body">
|
||||
<form [formGroup]="form60Form" (ngSubmit)="submit()">
|
||||
@@ -204,7 +204,7 @@
|
||||
<button type="button" mat-stroked-button (click)="toggleEditMode()">Cancel</button>
|
||||
}
|
||||
<button type="submit" mat-flat-button color="primary" [disabled]="!form60Form.valid">
|
||||
<mat-icon>check</mat-icon> Submit Form 60
|
||||
<mat-icon>check</mat-icon> Submit Form 97
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -224,14 +224,14 @@ export class Form60TabComponent implements OnInit, OnDestroy {
|
||||
this.isEditMode = false;
|
||||
this.alertService.alert({
|
||||
type: 'success',
|
||||
message: 'Form 60 submitted successfully.'
|
||||
message: 'Form 97 submitted successfully.'
|
||||
});
|
||||
this.loadForm60Data();
|
||||
},
|
||||
error: (err: any) => {
|
||||
this.alertService.alert({
|
||||
type: 'error',
|
||||
message: err?.error?.developerMessage || err?.error?.defaultUserMessage || 'Failed to submit Form 60.'
|
||||
message: err?.error?.developerMessage || err?.error?.defaultUserMessage || 'Failed to submit Form 97.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
<mifosx-client-kyc-step
|
||||
#clientKyc
|
||||
[isNonIndividual]="legalFormType === 2"
|
||||
[isMinor]="clientGeneralStep?.isMinor || false"
|
||||
[clientId]="createdClientId"
|
||||
[constitutionMeta]="clientGeneralStep?.selectedConstitution"
|
||||
[entityName]="clientGeneralStep?.createClientForm?.get('fullname')?.value || ''"
|
||||
@@ -146,6 +147,7 @@
|
||||
}
|
||||
<mifosx-client-india-address-step
|
||||
#clientIndiaAddress
|
||||
[isMinor]="clientGeneralStep?.isMinor || false"
|
||||
[readOnly]="isTabReadOnly(addressTabIdx)"
|
||||
></mifosx-client-india-address-step>
|
||||
</div>
|
||||
|
||||
@@ -111,6 +111,12 @@ export class CreateClientComponent {
|
||||
this.route.data.subscribe((data: { clientTemplate: any; clientAddressFieldConfig: any }) => {
|
||||
this.clientTemplate = data.clientTemplate;
|
||||
this.clientAddressFieldConfig = data.clientAddressFieldConfig;
|
||||
// This app's RouteReusableStrategy keeps this SAME component instance alive across
|
||||
// repeat visits to /clients/create, so ngOnInit/constructor won't re-run for a second
|
||||
// "create new client" session — reset transient state here instead, since `route.data`
|
||||
// re-emits on every navigation regardless of whether the component was reused. Must run
|
||||
// BEFORE setDatatables(), which reads legalFormType to filter clientTemplate.datatables.
|
||||
this.resetForNewClient();
|
||||
this.setDatatables();
|
||||
});
|
||||
// Read pre-fill data set by "Create as Client" from a manual family member
|
||||
@@ -121,6 +127,20 @@ export class CreateClientComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/** Resets session state that could otherwise leak between clients when the route is reused. */
|
||||
private resetForNewClient(): void {
|
||||
this.legalFormType = 1;
|
||||
this.selectedTabIndex = 0;
|
||||
this.createdClientId = '';
|
||||
this.savingClient = false;
|
||||
this.saveProgress = 0;
|
||||
this.tabEditMode = {};
|
||||
this.highestTabReached = 0;
|
||||
this.validationError = '';
|
||||
this.prefillData = null;
|
||||
this.clientGeneralStep?.resetForNewClient();
|
||||
}
|
||||
|
||||
/** Check if a tab is in read-only mode (completed tab navigated back to) */
|
||||
isTabReadOnly(tabIndex: number): boolean {
|
||||
// Only tabs that have been passed (below highest reached) AND not explicitly set to edit mode
|
||||
|
||||
@@ -149,6 +149,7 @@
|
||||
<mifosx-client-kyc-step
|
||||
#clientKyc
|
||||
[isNonIndividual]="legalFormType === 2"
|
||||
[isMinor]="clientGeneralStep?.isMinor || false"
|
||||
[clientId]="clientId"
|
||||
[readOnly]="isTabReadOnly(1)"
|
||||
></mifosx-client-kyc-step>
|
||||
@@ -206,6 +207,7 @@
|
||||
<mifosx-client-india-address-step
|
||||
#clientIndiaAddress
|
||||
[clientId]="clientId"
|
||||
[isMinor]="clientGeneralStep?.isMinor || false"
|
||||
[readOnly]="isTabReadOnly(addressTabIdx)"
|
||||
></mifosx-client-india-address-step>
|
||||
</div>
|
||||
|
||||
@@ -70,6 +70,19 @@ export const PROOF_META: Record<string, Partial<ProofType>> = {
|
||||
documentKeyPlaceholder: 'NPR reference number',
|
||||
info: 'NPR letter — upload full document.'
|
||||
},
|
||||
BIRTH_CERTIFICATE: {
|
||||
sides: 1,
|
||||
documentKeyLabel: 'Birth certificate registration number',
|
||||
documentKeyPlaceholder: 'Registration / certificate number',
|
||||
category: 'Government-issued ID',
|
||||
info: 'Birth certificate issued by the Municipal Corporation / Registrar of Births & Deaths — accepted proof of identity for minor clients as per RBI KYC norms.'
|
||||
},
|
||||
SCHOOL_ID_CARD: {
|
||||
sides: 1,
|
||||
documentKeyLabel: 'School ID / admission number',
|
||||
documentKeyPlaceholder: 'Student ID number',
|
||||
info: 'School identity card bearing photograph — accepted proof of identity for minor clients as per RBI KYC norms.'
|
||||
},
|
||||
UTILITY_BILL: {
|
||||
sides: 1,
|
||||
category: 'Utility bills',
|
||||
|
||||
Reference in New Issue
Block a user