feat: update kyc tab, branch day register, and dormancy reports
Tests / testRigor (push) Canceled after 0s
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s

This commit is contained in:
2026-09-10 18:29:33 +05:30
parent f0c06988c0
commit fd5fe6e3b2
16 changed files with 595 additions and 104 deletions
@@ -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';