Merge remote-tracking branch 'origin/uate' into uate

This commit is contained in:
2026-07-28 12:37:59 +05:30
6 changed files with 180 additions and 26 deletions
@@ -238,6 +238,42 @@ export class ClientIndiaAddressStepComponent implements OnInit, OnChanges {
}
}
/**
* Resets transient address-tab state for a brand-new client.
*
* This app's RouteReusableStrategy (core/route/route-reusable-strategy.ts) keeps this SAME
* component instance alive across repeat visits to /clients/create, so `ngOnInit` never
* re-runs for a second "create new client" session — the previous client's address forms,
* selected proof-of-address chip and uploaded files would otherwise leak into the new one.
* Called by CreateClientComponent.resetForNewClient() whenever a fresh create session begins.
*/
resetForNewClient(): void {
this.permanentForm.reset({ country: 'India' });
this.currentForm.reset({ country: 'India' });
this.officeForm.reset({ country: 'India' });
this.correspondenceForm.reset({ country: 'India' });
this.activeTab = 0;
this.sameAsPermanent = false;
this.pinStatus = {
permanent: { loading: false, found: false, error: false, message: '' },
current: { loading: false, found: false, error: false, message: '' },
office: { loading: false, found: false, error: false, message: '' },
correspondence: { loading: false, found: false, error: false, message: '' }
};
this.localityOptions = { permanent: [], current: [], office: [], correspondence: [] };
this.selectedAddrProof = null;
this.addrFiles = { front: null, back: null };
this.addrMergedFile = null;
this.addrMergedPreview = null;
this.existingAddressProofIdentifierId = null;
this.existingAddressProofDocuments = [];
this.addressLoaded = false;
// Re-fetch rather than re-filter `addressProofs` in place — if the previous session's
// isMinor filtering already reduced it (e.g. down to Aadhaar/Passport only), filtering
// that reduced list again can never recover the items it already removed.
this.loadProofTypes();
}
private loadProofTypes(): void {
this.loadingProofs = true;
this.clientsService.getKycProofTypes().subscribe({
@@ -161,6 +161,17 @@
}
</button>
}
@if (aadhaarOtpSent && aadhaarVerifyStatus !== 'VERIFIED') {
<button
mat-stroked-button
type="button"
class="change-number-btn"
[disabled]="readOnly"
(click)="changeAadhaarNumber()"
>
<mat-icon>edit</mat-icon> Change Number
</button>
}
@if (aadhaarVerifyStatus === 'VERIFIED') {
<span class="verify-chip verified"><fa-icon icon="check-circle"></fa-icon> Verified</span>
}
@@ -266,6 +266,23 @@
gap: 6px;
}
.change-number-btn {
font-size: 12px !important;
font-weight: 600 !important;
padding: 0 14px !important;
height: 36px !important;
border-radius: 6px !important;
display: flex;
align-items: center;
gap: 6px;
mat-icon {
font-size: 16px;
width: 16px;
height: 16px;
}
}
.verify-chip {
display: inline-flex;
align-items: center;
@@ -462,15 +462,16 @@ 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'
];
/** RBI-accepted OVDs for a minor client — mirrors the backend's minor branch in
* ClientKycApiResource.getRequiredDocuments(). Voter ID, Driving Licence, PAN and NREGA
* all require adult eligibility; National Population Register Letter is deliberately
* excluded too (not offered as a minor OVD in this system). */
private static readonly MINOR_ELIGIBLE_DOC_CODES = new Set([
'AADHAAR',
'PASSPORT',
'BIRTH_CERTIFICATE',
'SCHOOL_ID_CARD'
]);
/** Birth Certificate / School ID Card are minor-specific OVDs — not relevant once the
* client is an adult, so they're hidden the other direction. */
@@ -481,27 +482,13 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
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_ELIGIBLE_DOC_CODES.has((d.code || '').toUpperCase()));
}
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.filterDocTypesForMinor(
this.defaultDocumentTypes.map((d) => ({
name: d.name,
code: d.code,
mandatory: false
}))
);
this.rebuildKycDocPicker();
}
this.seedDefaultDocTypes();
this.loadExistingKycIfNeeded();
this.loadKycTemplate();
if (this.isNonIndividual && this.constitutionMeta) {
@@ -509,6 +496,92 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
}
}
/** Seed the document-type picker from defaults, filtered by the current isMinor input.
* Only seeds when the list is empty — the template API (loadKycTemplate) takes over once
* a clientId is available. */
private seedDefaultDocTypes(): void {
if (this.kycDocumentTypes.length) return;
this.kycDocumentTypes = this.filterDocTypesForMinor(
this.defaultDocumentTypes.map((d) => ({
name: d.name,
code: d.code,
mandatory: false
}))
);
this.rebuildKycDocPicker();
}
/**
* Resets transient KYC-tab state for a brand-new client.
*
* This app's RouteReusableStrategy (core/route/route-reusable-strategy.ts) keeps this SAME
* component instance alive across repeat visits to /clients/create, so `ngOnInit` never
* re-runs for a second "create new client" session. Without this, the document-type picker
* (and any PAN/Aadhaar verification state, uploaded photo/signature) would otherwise leak
* from whatever client was created in the previous session into the new one. Called by
* CreateClientComponent.resetForNewClient() whenever a fresh create session begins.
*/
resetForNewClient(): void {
this.kycForm.reset({
hasPan: true,
riskCategory: 1,
amlCheckStatus: 'CLEAR',
bannedEntityMatch: 'NO',
willfulDefaulter: 'NO',
ecgcSalFlag: 'NO',
dormantFlag: 'ACTIVE',
accountStatus: 'ACTIVE',
form60Type: 'FORM_60',
form15Type: '15G'
});
this.kycId = '';
this.kycLoaded = false;
this.kycStatus = 0;
this.kycTemplate = null;
this.kycTemplateLoaded = false;
this.kycDocumentTypes = [];
this.selectedKycDoc = null;
this.kycDocFiles = { front: null, back: null };
this.kycDocMergedFile = null;
this.kycDocMergedPreview = null;
this.existingKycIdentifierId = null;
this.existingKycDocuments = [];
this.photoSigTouched = false;
this.panVerifyStatus = 'NONE';
this.panVerifying = false;
this.panVerifyError = '';
this.panVerifyName = '';
this.panVerifiedOn = '';
this.aadhaarVerifyStatus = 'NONE';
this.aadhaarVerifying = false;
this.aadhaarVerifyError = '';
this.aadhaarVerifyName = '';
this.aadhaarVisible = false;
this.aadhaarOtpSent = false;
this.resetOtpDigits();
this.aadhaarOtpReferenceId = '';
this.panAadhaarSeedingStatus = '';
this.entPanVerifyStatus = 'NONE';
this.entPanVerifying = false;
this.entPanVerifyName = '';
this.entPanVerifiedOn = '';
this.gstinVerifyStatus = 'NONE';
this.gstinVerifying = false;
this.gstinVerifyName = '';
this.gstinVerifiedOn = '';
this.gstinVerifyState = '';
this.cinVerifyStatus = 'NONE';
this.cinVerifying = false;
this.cinVerifiedOn = '';
this.cinCompanyName = '';
this.photographFile = null;
this.photographPreview = null;
this.signatureFile = null;
this.signaturePreview = null;
this.existingSignatureDocId = '';
this.seedDefaultDocTypes();
}
ngOnDestroy(): void {
if (this.cinProgressTimer) clearInterval(this.cinProgressTimer);
if (this.resendTimer) clearInterval(this.resendTimer);
@@ -1375,6 +1448,21 @@ export class ClientKycStepComponent implements OnInit, OnChanges, OnDestroy {
);
}
/** Lets the user go back and fix a wrong Aadhaar number after an OTP was already sent —
* unlocks the (readonly-while-OTP-pending) input and discards the in-flight OTP session,
* since it was requested for the now-being-corrected number. */
changeAadhaarNumber(): void {
this.aadhaarOtpSent = false;
this.aadhaarVerifyError = '';
this.aadhaarOtpReferenceId = '';
this.resetOtpDigits();
this.resendCountdown = 0;
if (this.resendTimer) {
clearInterval(this.resendTimer);
this.resendTimer = null;
}
}
/** Verify Aadhaar OTP */
verifyAadhaarOtp() {
if (!this.aadhaarOtp || !this.clientId || !this.kycId) return;
@@ -139,6 +139,8 @@ export class CreateClientComponent {
this.validationError = '';
this.prefillData = null;
this.clientGeneralStep?.resetForNewClient();
this.clientKycStep?.resetForNewClient();
this.clientIndiaAddressStep?.resetForNewClient();
}
/** Check if a tab is in read-only mode (completed tab navigated back to) */