Merge remote-tracking branch 'origin/demo' into demo
This commit is contained in:
@@ -33,6 +33,7 @@ import { ContactOtpBlockComponent, ContactOtpState } from './contact-otp-block/c
|
||||
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { MatProgressSpinner } from '@angular/material/progress-spinner';
|
||||
@@ -51,6 +52,7 @@ import { ORIGINAL_VALUES_PAYLOAD_KEY, buildOriginalValuesSnapshot } from '../sha
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTabsModule,
|
||||
MatIconModule,
|
||||
MatCheckboxModule,
|
||||
MatDivider,
|
||||
FaIconComponent,
|
||||
MatProgressSpinner,
|
||||
@@ -108,6 +110,13 @@ export class EditClientComponent {
|
||||
newMobileOtpId: number | null = null;
|
||||
oldMobileVerified = false;
|
||||
newMobileVerified = false;
|
||||
|
||||
// FIR exception flow properties for inaccessible old mobile number
|
||||
oldMobileNotAccessible = false;
|
||||
firFileName = '';
|
||||
firDocumentId: number | null = null;
|
||||
uploadingFir = false;
|
||||
|
||||
oldEmailOtpId: number | null = null;
|
||||
newEmailOtpId: number | null = null;
|
||||
oldEmailVerified = false;
|
||||
@@ -215,11 +224,14 @@ export class EditClientComponent {
|
||||
return this.detectSensitiveFieldChanges(this.clientDataAndTemplate);
|
||||
}
|
||||
|
||||
/** Whether every OTP block required by the fields that actually changed has been verified. */
|
||||
/** Whether every OTP block required by the fields that actually changed has been verified (or FIR uploaded for lost mobile). */
|
||||
get contactVerificationComplete(): boolean {
|
||||
const changes = this.contactChanges;
|
||||
if (changes.mobileChanged && !(this.oldMobileVerified && this.newMobileVerified)) {
|
||||
return false;
|
||||
if (changes.mobileChanged) {
|
||||
const isOldSatisfied = this.oldMobileNotAccessible ? this.firDocumentId != null : this.oldMobileVerified;
|
||||
if (!isOldSatisfied || !this.newMobileVerified) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (changes.emailChanged && !(this.oldEmailVerified && this.newEmailVerified)) {
|
||||
return false;
|
||||
@@ -227,6 +239,41 @@ export class EditClientComponent {
|
||||
return true;
|
||||
}
|
||||
|
||||
onOldMobileNotAccessibleToggle(checked?: boolean): void {
|
||||
if (checked !== undefined) {
|
||||
this.oldMobileNotAccessible = checked;
|
||||
}
|
||||
if (this.oldMobileNotAccessible) {
|
||||
this.oldMobileOtpId = null;
|
||||
this.oldMobileVerified = false;
|
||||
}
|
||||
}
|
||||
|
||||
onFirFileSelected(event: any): void {
|
||||
const file: File = event.target?.files?.[0];
|
||||
if (!file) return;
|
||||
this.firFileName = file.name;
|
||||
this.uploadingFir = true;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('name', 'FIR - Lost Mobile Number');
|
||||
formData.append('description', 'FIR police complaint copy for lost mobile number verification exception');
|
||||
|
||||
this.clientsService.uploadClientDocument(this.clientId, formData).subscribe({
|
||||
next: (res: any) => {
|
||||
this.uploadingFir = false;
|
||||
this.firDocumentId = res?.resourceId || res?.id || null;
|
||||
this.snackBar.open('FIR document uploaded successfully', 'Close', { duration: 3000 });
|
||||
},
|
||||
error: () => {
|
||||
this.uploadingFir = false;
|
||||
this.firDocumentId = null;
|
||||
this.snackBar.open('Failed to upload FIR document', 'Close', { duration: 4000 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onOldMobileOtpState(state: ContactOtpState): void {
|
||||
this.oldMobileOtpId = state.otpId;
|
||||
this.oldMobileVerified = state.verified;
|
||||
@@ -254,6 +301,10 @@ export class EditClientComponent {
|
||||
this.newMobileOtpId = null;
|
||||
this.oldMobileVerified = false;
|
||||
this.newMobileVerified = false;
|
||||
this.oldMobileNotAccessible = false;
|
||||
this.firFileName = '';
|
||||
this.firDocumentId = null;
|
||||
this.uploadingFir = false;
|
||||
this.oldEmailOtpId = null;
|
||||
this.newEmailOtpId = null;
|
||||
this.oldEmailVerified = false;
|
||||
@@ -439,7 +490,11 @@ export class EditClientComponent {
|
||||
// email's when BOTH changed in the same save. See the class-level JSDoc above the OTP
|
||||
// state fields for the full naming rationale.
|
||||
if (changedFields.mobileChanged) {
|
||||
clientData.mobileOldOtpId = this.oldMobileOtpId;
|
||||
if (this.oldMobileNotAccessible && this.firDocumentId) {
|
||||
(clientData as any).oldMobileVerificationExceptionDocumentId = this.firDocumentId;
|
||||
} else {
|
||||
clientData.mobileOldOtpId = this.oldMobileOtpId;
|
||||
}
|
||||
clientData.mobileNewOtpId = this.newMobileOtpId;
|
||||
}
|
||||
if (changedFields.emailChanged) {
|
||||
@@ -630,25 +685,34 @@ export class EditClientComponent {
|
||||
|
||||
private detectSensitiveFieldChanges(originalData: any) {
|
||||
const form = this.clientGeneralForm;
|
||||
// Trimmed so browser autofill adding invisible leading/trailing whitespace never reads as
|
||||
// a "change" — the masked preview shown in the Verify Contact Change panel only reveals the
|
||||
// first couple of characters, so a whitespace-only difference is otherwise invisible to the
|
||||
// user and just looks like the panel appeared for no reason. These trimmed-but-original-case
|
||||
// values are what get returned/sent as the actual OTP destination and saved value — only the
|
||||
// *comparison* (below) is further case-normalized for email, so we never silently rewrite the
|
||||
// customer's email casing into the saved record or the OTP destination.
|
||||
const newMobile = (form?.get('mobileNo')?.value || '').trim();
|
||||
const newEmail = (form?.get('emailAddress')?.value || '').trim();
|
||||
const newDobRaw = form?.get('dateOfBirth')?.value;
|
||||
// Local calendar date, NOT `.toISOString()` — that converts to UTC first, which silently
|
||||
// shifts the date by a day in any positive-UTC-offset timezone (e.g. India, UTC+5:30:
|
||||
// midnight local on the 15th becomes 18:30 UTC on the 14th). That made this comparison
|
||||
// report a DOB "change" — falsely triggering the Re-KYC dialog — even when the user never
|
||||
// touched the date field, purely because the Date object round-tripped through the picker.
|
||||
if (!form || !originalData) {
|
||||
return {
|
||||
nameChanged: false,
|
||||
mobileChanged: false,
|
||||
emailChanged: false,
|
||||
dobChanged: false,
|
||||
oldName: '',
|
||||
newName: '',
|
||||
oldMobile: '',
|
||||
newMobile: '',
|
||||
oldEmail: '',
|
||||
newEmail: '',
|
||||
oldDob: '',
|
||||
newDob: ''
|
||||
};
|
||||
}
|
||||
|
||||
const mobileControl = form.get('mobileNo');
|
||||
const emailControl = form.get('emailAddress');
|
||||
|
||||
// Trimmed so browser autofill adding invisible leading/trailing whitespace never reads as a change
|
||||
const newMobile = (mobileControl?.value || '').toString().trim();
|
||||
const newEmail = (emailControl?.value || '').toString().trim();
|
||||
const newDobRaw = form.get('dateOfBirth')?.value;
|
||||
const newDob = newDobRaw instanceof Date ? this.toLocalDateString(newDobRaw) : newDobRaw || '';
|
||||
|
||||
const oldMobile = (originalData.mobileNo || '').trim();
|
||||
const oldEmail = (originalData.emailAddress || '').trim();
|
||||
const oldMobile = (originalData.mobileNo || '').toString().trim();
|
||||
const oldEmail = (originalData.emailAddress || '').toString().trim();
|
||||
const oldDobRaw = originalData.dateOfBirth;
|
||||
const oldDob = Array.isArray(oldDobRaw)
|
||||
? `${oldDobRaw[0]}-${String(oldDobRaw[1]).padStart(2, '0')}-${String(oldDobRaw[2]).padStart(2, '0')}`
|
||||
@@ -669,10 +733,10 @@ export class EditClientComponent {
|
||||
).trim();
|
||||
|
||||
const newName = buildName({
|
||||
firstname: form?.get('firstname')?.value || '',
|
||||
middlename: form?.get('middlename')?.value || '',
|
||||
lastname: form?.get('lastname')?.value || '',
|
||||
fullname: form?.get('fullname')?.value || ''
|
||||
firstname: form.get('firstname')?.value || '',
|
||||
middlename: form.get('middlename')?.value || '',
|
||||
lastname: form.get('lastname')?.value || '',
|
||||
fullname: form.get('fullname')?.value || ''
|
||||
});
|
||||
const oldName = buildName({
|
||||
firstname: originalData.firstname || '',
|
||||
@@ -681,12 +745,21 @@ export class EditClientComponent {
|
||||
fullname: originalData.fullname || ''
|
||||
});
|
||||
|
||||
// Normalize phone numbers by stripping non-digit characters (+91 9009009009 -> 9009009009)
|
||||
const normPhone = (val: string) => val.replace(/\D/g, '');
|
||||
const newMobileNorm = normPhone(newMobile);
|
||||
const oldMobileNorm = normPhone(oldMobile);
|
||||
|
||||
// Mobile/Email are considered changed ONLY when the control has been edited (dirty) AND normalized values differ
|
||||
const mobileChanged =
|
||||
!!oldMobileNorm && !!newMobileNorm && !!mobileControl?.dirty && newMobileNorm !== oldMobileNorm;
|
||||
const emailChanged =
|
||||
!!oldEmail && !!newEmail && !!emailControl?.dirty && newEmail.toLowerCase() !== oldEmail.toLowerCase();
|
||||
|
||||
return {
|
||||
nameChanged: newName !== oldName,
|
||||
mobileChanged: newMobile !== oldMobile,
|
||||
// Case-insensitive: emails are not case-sensitive in practice, and comparing raw case
|
||||
// would flag a change (surfacing the dual-OTP panel) even when nothing meaningful changed.
|
||||
emailChanged: newEmail.toLowerCase() !== oldEmail.toLowerCase(),
|
||||
mobileChanged,
|
||||
emailChanged,
|
||||
dobChanged: newDob !== oldDob,
|
||||
oldName,
|
||||
newName,
|
||||
|
||||
Reference in New Issue
Block a user