minor change on client
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 688 KiB |
+86
-1
@@ -466,6 +466,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.
|
||||
@@ -521,7 +566,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 || '',
|
||||
@@ -599,6 +648,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,6 +28,8 @@
|
||||
<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. -->
|
||||
@@ -52,9 +54,10 @@
|
||||
<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) => ({
|
||||
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 */
|
||||
@@ -874,11 +922,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) => ({
|
||||
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) {
|
||||
@@ -891,11 +941,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) => ({
|
||||
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',
|
||||
|
||||
@@ -1468,13 +1468,13 @@
|
||||
Balance Certificate
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/letter-mgmt/form-60" tabindex="0">
|
||||
<fa-icon icon="file-signature" class="menu-item-icon" style="color: #15803d"></fa-icon> Form 60
|
||||
<fa-icon icon="file-signature" class="menu-item-icon" style="color: #15803d"></fa-icon> Form 97
|
||||
</button>
|
||||
<button mat-menu-item tabindex="0">
|
||||
<fa-icon icon="file-signature" class="menu-item-icon" style="color: #15803d"></fa-icon> Form 61
|
||||
<fa-icon icon="file-signature" class="menu-item-icon" style="color: #15803d"></fa-icon> Form 98
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/letter-mgmt/form-15gh" tabindex="0">
|
||||
<fa-icon icon="file-signature" class="menu-item-icon" style="color: #15803d"></fa-icon> Form 15G and H
|
||||
<fa-icon icon="file-signature" class="menu-item-icon" style="color: #15803d"></fa-icon> Form 121
|
||||
</button>
|
||||
<button mat-menu-item routerLink="/letter-mgmt/hypothecation-certificate" tabindex="0">
|
||||
<fa-icon icon="file-alt" class="menu-item-icon" style="color: #15803d"></fa-icon>
|
||||
|
||||
@@ -969,7 +969,7 @@ export function buildForm60Pdf(r: Form60Record): jsPDF {
|
||||
|
||||
let y = paintLetterhead(
|
||||
c,
|
||||
'Form 60 Declaration',
|
||||
'Form 97 Declaration',
|
||||
'CERTIFICATE REFERENCE',
|
||||
`F60-${r.customerId}`,
|
||||
'PAN Status',
|
||||
|
||||
+3
-3
@@ -85,7 +85,7 @@
|
||||
} @else if (isBalance && balance) {
|
||||
<span class="cpd-stage-pill">{{ balance.asOnDate | date: 'd MMM y' }}</span>
|
||||
} @else if (isForm60 && form60) {
|
||||
<span class="cpd-stage-pill cpd-stage-pill--amber">No PAN — Form 60</span>
|
||||
<span class="cpd-stage-pill cpd-stage-pill--amber">No PAN — Form 97</span>
|
||||
} @else if (isForm15gh && form15gh) {
|
||||
<span class="cpd-stage-pill cpd-stage-pill--teal">Form {{ form15gh.kind }}</span>
|
||||
} @else if (isHypothecation && hypothecation) {
|
||||
@@ -313,7 +313,7 @@
|
||||
</p>
|
||||
}
|
||||
|
||||
<!-- ── FORM 60 ── -->
|
||||
<!-- ── FORM 97 ── -->
|
||||
@if (isForm60 && form60) {
|
||||
<p class="cpd-para">
|
||||
Declaration under <strong>Section 114B</strong> of the Income-tax Rules, 1962, to be made by a person who does
|
||||
@@ -367,7 +367,7 @@
|
||||
<tr class="cpd-summary-total">
|
||||
<td colspan="3">PAN STATUS</td>
|
||||
<td>
|
||||
<strong>NOT ALLOTTED — Form 60 declared</strong>
|
||||
<strong>NOT ALLOTTED — Form 97 declared</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
+2
-2
@@ -151,9 +151,9 @@ export class CertificatePreviewDialogComponent {
|
||||
case 'balance':
|
||||
return 'Balance Certificate';
|
||||
case 'form60':
|
||||
return 'Form 60 Declaration';
|
||||
return 'Form 97 Declaration';
|
||||
case 'form15gh':
|
||||
return this.form15gh?.kind === '15H' ? 'Form 15H Declaration' : 'Form 15G Declaration';
|
||||
return 'Form 121 Declaration';
|
||||
case 'hypothecation':
|
||||
return 'Hypothecation Certificate';
|
||||
case 'noc':
|
||||
|
||||
@@ -11,11 +11,8 @@
|
||||
<div class="cert-head-l">
|
||||
<fa-icon icon="file-signature" class="cert-head-ic"></fa-icon>
|
||||
<div>
|
||||
<h2 class="cert-head-t">Form 15G & 15H</h2>
|
||||
<div class="cert-head-s">
|
||||
TDS-waiver declarations for FD / RD interest income —
|
||||
<strong>15G</strong> for individuals below 60 years, <strong>15H</strong> for senior citizens (60+).
|
||||
</div>
|
||||
<h2 class="cert-head-t">Form 121</h2>
|
||||
<div class="cert-head-s">TDS-waiver declarations for FD / RD interest income — Form 121.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -26,14 +23,14 @@
|
||||
<fa-icon icon="user" class="cert-kpi-ic"></fa-icon>
|
||||
<div>
|
||||
<div class="cert-kpi-n">{{ count15G() | number }}</div>
|
||||
<div class="cert-kpi-l">Form 15G (under 60)</div>
|
||||
<div class="cert-kpi-l">Form 121 (under 60)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cert-kpi cert-kpi--amber">
|
||||
<fa-icon icon="user-tie" class="cert-kpi-ic"></fa-icon>
|
||||
<div>
|
||||
<div class="cert-kpi-n">{{ count15H() | number }}</div>
|
||||
<div class="cert-kpi-l">Form 15H (60+)</div>
|
||||
<div class="cert-kpi-l">Form 121 (60+)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cert-kpi cert-kpi--green">
|
||||
@@ -63,11 +60,11 @@
|
||||
</div>
|
||||
|
||||
<div class="cert-filter">
|
||||
<label>Form</label>
|
||||
<label>Form Type</label>
|
||||
<select [(ngModel)]="kindFilter">
|
||||
<option value="">Both forms</option>
|
||||
<option value="15G">15G — Under 60</option>
|
||||
<option value="15H">15H — Senior citizens (60+)</option>
|
||||
<option value="">All Form 121</option>
|
||||
<option value="15G">Form 121 (Below 60 years)</option>
|
||||
<option value="15H">Form 121 (Senior Citizen 60+)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +89,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Customer · PAN</th>
|
||||
<th>Form</th>
|
||||
<th>Form Type</th>
|
||||
<th>Age</th>
|
||||
<th>Branch</th>
|
||||
<th class="num">Deposit Principal</th>
|
||||
@@ -115,7 +112,7 @@
|
||||
[class.cert-pill--blue]="formKind(r) === '15G'"
|
||||
[class.cert-pill--amber]="formKind(r) === '15H'"
|
||||
>
|
||||
Form {{ formKind(r) }}
|
||||
Form 121 ({{ formKind(r) }})
|
||||
</span>
|
||||
</td>
|
||||
<td class="small">{{ age(r.dob) }} yrs</td>
|
||||
|
||||
@@ -146,9 +146,9 @@ export class Form15ghComponent implements OnInit {
|
||||
const kind = this.formKind(r);
|
||||
this.dscSign.promptThenDownload(
|
||||
{
|
||||
title: `Form ${kind} Declaration`,
|
||||
title: `Form 121 (${kind}) Declaration`,
|
||||
reference: `${r.customerId} · ${r.customerName} · ${r.fy}`,
|
||||
filename: `Form-${kind}-${r.customerId}-${r.fy.replace(/\s+/g, '')}.pdf`
|
||||
filename: `Form121-${r.customerId}-${r.fy.replace(/\s+/g, '')}.pdf`
|
||||
},
|
||||
(dsc) => downloadForm15ghPdf(r, kind, dsc)
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="cert-head-l">
|
||||
<fa-icon icon="file-signature" class="cert-head-ic"></fa-icon>
|
||||
<div>
|
||||
<h2 class="cert-head-t">Form 60 Declaration</h2>
|
||||
<h2 class="cert-head-t">Form 97 Declaration</h2>
|
||||
<div class="cert-head-s">
|
||||
Income Tax Rule 114B — required for any customer transacting above the threshold who does
|
||||
<strong>not</strong> have a PAN on file.
|
||||
@@ -33,7 +33,7 @@
|
||||
<fa-icon icon="check-circle" class="cert-kpi-ic"></fa-icon>
|
||||
<div>
|
||||
<div class="cert-kpi-n">{{ totalFiled() | number }}</div>
|
||||
<div class="cert-kpi-l">Form 60 Filed</div>
|
||||
<div class="cert-kpi-l">Form 97 Filed</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cert-kpi cert-kpi--amber">
|
||||
@@ -58,7 +58,7 @@
|
||||
<div class="cert-filter">
|
||||
<label>PAN status</label>
|
||||
<select [(ngModel)]="panFilter">
|
||||
<option value="MISSING">Without PAN (Form 60 applicable)</option>
|
||||
<option value="MISSING">Without PAN (Form 97 applicable)</option>
|
||||
<option value="ON_FILE">With PAN on file</option>
|
||||
<option value="">All customers</option>
|
||||
</select>
|
||||
@@ -91,7 +91,7 @@
|
||||
<th>Trigger Transaction</th>
|
||||
<th class="num">Amount</th>
|
||||
<th>PAN Status</th>
|
||||
<th>Form 60</th>
|
||||
<th>Form 97</th>
|
||||
<th class="col-actions">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -146,7 +146,7 @@
|
||||
class="cert-icon-btn primary"
|
||||
[disabled]="r.hasPan"
|
||||
(click)="viewCertificate(r)"
|
||||
matTooltip="View / Print Form 60"
|
||||
matTooltip="View / Print Form 97"
|
||||
>
|
||||
<fa-icon icon="eye"></fa-icon>
|
||||
</button>
|
||||
|
||||
@@ -121,7 +121,7 @@ export class Form60Component implements OnInit {
|
||||
|
||||
viewCertificate(r: Form60Record): void {
|
||||
if (r.hasPan) {
|
||||
this.snack.open(`${r.customerName} already has a PAN on file — Form 60 is not applicable.`, 'OK', {
|
||||
this.snack.open(`${r.customerName} already has a PAN on file — Form 97 is not applicable.`, 'OK', {
|
||||
duration: 3200
|
||||
});
|
||||
return;
|
||||
@@ -139,14 +139,14 @@ export class Form60Component implements OnInit {
|
||||
|
||||
downloadCertificate(r: Form60Record): void {
|
||||
if (r.hasPan) {
|
||||
this.snack.open(`${r.customerName} already has a PAN on file — Form 60 is not applicable.`, 'OK', {
|
||||
this.snack.open(`${r.customerName} already has a PAN on file — Form 97 is not applicable.`, 'OK', {
|
||||
duration: 3200
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.dscSign.promptThenDownload(
|
||||
{
|
||||
title: 'Form 60 Declaration',
|
||||
title: 'Form 97 Declaration',
|
||||
reference: `${r.customerId} · ${r.customerName}`,
|
||||
filename: `Form60-${r.customerId}.pdf`
|
||||
},
|
||||
|
||||
@@ -44,12 +44,12 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'letter-mgmt/form-60',
|
||||
component: Form60Component,
|
||||
data: { title: 'Form 60', breadcrumb: 'Form 60' }
|
||||
data: { title: 'Form 97', breadcrumb: 'Form 97' }
|
||||
},
|
||||
{
|
||||
path: 'letter-mgmt/form-15gh',
|
||||
component: Form15ghComponent,
|
||||
data: { title: 'Form 15G & 15H', breadcrumb: 'Form 15G/H' }
|
||||
data: { title: 'Form 121', breadcrumb: 'Form 121' }
|
||||
},
|
||||
{
|
||||
path: 'letter-mgmt/hypothecation-certificate',
|
||||
|
||||
@@ -2084,8 +2084,8 @@
|
||||
"Floating Rates": "Floating Rates",
|
||||
"Floating rate": "Floating Rate",
|
||||
"Follow": "Follow",
|
||||
"Form 60": "Form 60",
|
||||
"Form 60 Declaration": "Form 60 Declaration",
|
||||
"Form 60": "Form 97",
|
||||
"Form 60 Declaration": "Form 97 Declaration",
|
||||
"Frequency": "Frequency",
|
||||
"Frequency Interval": "Frequency Interval",
|
||||
"Frequency Interval for compounding": "Frequency Interval for compounding",
|
||||
@@ -3983,7 +3983,7 @@
|
||||
"Organization available languages": "Organization can select a language here from the available languages here.",
|
||||
"Original Schedule": "Original Schedule",
|
||||
"Overdue Charges": "Overdue Charges",
|
||||
"PAN not provided. Please fill Form 60 as per Income Tax Rule 114B.": "PAN not provided. Please fill Form 60 as per Income Tax Rule 114B.",
|
||||
"PAN not provided. Please fill Form 60 as per Income Tax Rule 114B.": "PAN not provided. Please fill Form 97 as per Income Tax Rule 114B.",
|
||||
"Password Preferences": "Password Preferences",
|
||||
"Passwords": "Passwords",
|
||||
"Pay Charge": "Pay Charge",
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
/* tslint:disable */
|
||||
export default {
|
||||
'mifos_x': {
|
||||
'version': '260723',
|
||||
'hash': 'ca85bae4'
|
||||
'version': '260728',
|
||||
'hash': 'a9c60083'
|
||||
},
|
||||
'allow_switching_backend_instance': true
|
||||
};
|
||||
/* tslint:enable */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user