feat: update kyc tab, branch day register, and dormancy reports
This commit is contained in:
@@ -126,6 +126,12 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
1,
|
||||
[Validators.required]
|
||||
],
|
||||
// The officially valid document, so a lapsed one can be re-collected on this form. The
|
||||
// backend has always accepted these four; only the fields were missing.
|
||||
kycDocumentName: [''],
|
||||
kycDocumentNumber: [''],
|
||||
kycDocIssueDate: [null],
|
||||
kycDocExpiryDate: [null],
|
||||
panNumber: [
|
||||
'',
|
||||
[Validators.pattern(/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/)]
|
||||
@@ -176,6 +182,10 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
// the full 12-digit number to re-verify, so don't pre-fill.
|
||||
aadhaarNumber: '',
|
||||
gstinNumber: data.gstinNumber || '',
|
||||
kycDocumentName: data.kycDocumentName || '',
|
||||
kycDocumentNumber: data.kycDocumentNumber || '',
|
||||
kycDocIssueDate: this.parseApiDate(data.kycDocIssueDate),
|
||||
kycDocExpiryDate: this.parseApiDate(data.kycDocExpiryDate),
|
||||
remarks: data.remarks || '',
|
||||
isException: data.isException || false,
|
||||
exceptionReason: data.exceptionReason ? data.exceptionReason.split(', ') : []
|
||||
@@ -355,6 +365,41 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
* stored record; either way the customer has cleared that check. Verifying one of two
|
||||
* outstanding items narrows the exception to the remaining one rather than closing it.
|
||||
*/
|
||||
/**
|
||||
* The officially valid document, for the update payload.
|
||||
*
|
||||
* Only sent when a document is named, so an officer editing the risk category on a client who
|
||||
* never had an OVD recorded does not silently blank the other three columns.
|
||||
*/
|
||||
private buildDocumentFields() {
|
||||
const name = (this.kycForm.value.kycDocumentName || '').trim();
|
||||
if (!name) {
|
||||
return {};
|
||||
}
|
||||
const dateFormat = 'dd MMMM yyyy';
|
||||
return {
|
||||
kycDocumentName: name,
|
||||
kycDocumentNumber: (this.kycForm.value.kycDocumentNumber || '').trim() || null,
|
||||
kycDocIssueDate: this.kycForm.value.kycDocIssueDate
|
||||
? this.dateUtils.formatDate(this.kycForm.value.kycDocIssueDate, dateFormat)
|
||||
: null,
|
||||
// Blank is meaningful: a passport expires, a PAN card does not.
|
||||
kycDocExpiryDate: this.kycForm.value.kycDocExpiryDate
|
||||
? this.dateUtils.formatDate(this.kycForm.value.kycDocExpiryDate, dateFormat)
|
||||
: null
|
||||
};
|
||||
}
|
||||
|
||||
/** True while the banner is asking for a document, so the form shows the fields to supply it. */
|
||||
get kycDocumentNeedsUpdate(): boolean {
|
||||
return this.showKycDocumentAlert;
|
||||
}
|
||||
|
||||
/** Today, as the latest an existing document can have been issued. */
|
||||
get maxDate(): Date {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
private buildExceptionFields() {
|
||||
const panVerified = this.verificationStatus.pan === 'VERIFIED' || this.kycData?.panVerified === true;
|
||||
const aadhaarVerified = this.verificationStatus.aadhaar === 'VERIFIED' || this.kycData?.aadhaarVerified === true;
|
||||
@@ -392,6 +437,7 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
remarks: this.kycForm.value.remarks || null,
|
||||
// dateFormat is required so the backend can parse exceptionResolvedOn when an exception closes.
|
||||
dateFormat: 'dd MMMM yyyy',
|
||||
...this.buildDocumentFields(),
|
||||
...this.buildExceptionFields()
|
||||
};
|
||||
|
||||
@@ -478,11 +524,12 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
if (response?.changes?.panVerified) {
|
||||
this.verificationStatus.pan = 'VERIFIED';
|
||||
this.verificationDetails.pan = response.changes;
|
||||
// A PAN that now verifies makes any earlier declaration moot.
|
||||
// A PAN that now verifies makes any earlier declaration moot. That write already
|
||||
// re-derives the exception columns, so it settles the exception too.
|
||||
if (this.panDeclarationTaken) {
|
||||
this.writePanDeclaration(null);
|
||||
} else {
|
||||
this.loadKycData();
|
||||
this.syncExceptionAfterVerify();
|
||||
}
|
||||
} else {
|
||||
this.verificationStatus.pan = 'FAILED';
|
||||
@@ -660,6 +707,60 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-derives the exception columns once a verification has actually passed.
|
||||
*
|
||||
* `saveKycBeforeVerify()` writes the exception state *before* the vendor is called, when the
|
||||
* check is by definition still outstanding — so a check that then succeeds leaves the record
|
||||
* carrying an exception nobody closes. The KYC tab reads `panVerified` and shows "Verified"
|
||||
* while the exceptions register keeps reading `isException`, which is why a client could sit in
|
||||
* the Pending tab with every document verified.
|
||||
*
|
||||
* Called on each verification success. `buildExceptionFields()` already prefers the in-session
|
||||
* `verificationStatus` over the stored record, so the freshly passed check counts even though
|
||||
* `kycData` has not been reloaded yet; `hadException` still reads the stored value, which is
|
||||
* what tells it to close the entry rather than leave every flag off.
|
||||
*
|
||||
* Verifying one of two outstanding items narrows the exception instead of closing it — that
|
||||
* falls out of the derivation, so this needs no special case for it.
|
||||
*/
|
||||
private syncExceptionAfterVerify(): void {
|
||||
if (!this.kycId) {
|
||||
this.loadKycData();
|
||||
return;
|
||||
}
|
||||
|
||||
const exceptionFields = this.buildExceptionFields();
|
||||
|
||||
// Nothing recorded and nothing outstanding — no write would change anything.
|
||||
if (this.kycData?.isException !== true && !exceptionFields.isException) {
|
||||
this.loadKycData();
|
||||
return;
|
||||
}
|
||||
|
||||
this.clientsService
|
||||
.updateClientKyc(this.clientId, this.kycId, {
|
||||
locale: 'en',
|
||||
dateFormat: 'dd MMMM yyyy',
|
||||
riskCategory: this.kycForm.value.riskCategory || 1,
|
||||
...exceptionFields
|
||||
})
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
// The verification itself succeeded either way, so a failure here must not read as a
|
||||
// failed check — surface it as the save problem it is and still refresh.
|
||||
next: () => this.loadKycData(),
|
||||
error: (err: any) => {
|
||||
this.errorMessage =
|
||||
err?.error?.developerMessage ||
|
||||
err?.error?.defaultUserMessage ||
|
||||
err?.message ||
|
||||
'Verification succeeded but the KYC exception could not be updated.';
|
||||
this.loadKycData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the UIDAI enrolment receipt accepted in place of a verified Aadhaar.
|
||||
*
|
||||
@@ -786,6 +887,12 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
this.verificationError.aadhaar = null;
|
||||
}
|
||||
|
||||
/** Abandons the OTP exchange, so a mis-click can be backed out of without reloading the tab. */
|
||||
cancelAadhaarReVerify(): void {
|
||||
this.resetOtpState();
|
||||
this.verificationError.aadhaar = null;
|
||||
}
|
||||
|
||||
/** Step 1: Send OTP to Aadhaar-linked mobile */
|
||||
sendAadhaarOtp(): void {
|
||||
const aadhaar = this.isEditMode ? this.kycForm?.get('aadhaarNumber')?.value : this.aadhaarFullNumber;
|
||||
@@ -855,7 +962,7 @@ export class KycTabComponent implements OnInit, OnDestroy {
|
||||
if (this.aadhaarReceiptTaken) {
|
||||
this.writeAadhaarReceipt(null);
|
||||
} else {
|
||||
this.loadKycData();
|
||||
this.syncExceptionAfterVerify();
|
||||
}
|
||||
} else {
|
||||
this.verificationStatus.aadhaar = 'FAILED';
|
||||
|
||||
Reference in New Issue
Block a user