feat: MFA/2FA, OTP audit logs, KYC + client/user enhancements
- Access Control module: SMS/Email/TOTP/Hardware-token MFA factor management - OTP Audit Logs module under Admin menu (list + detail) with 401/403 handling - Two-factor authentication login flow updates - Client KYC, India address, family-members, ckyc, nominee enhancements - New shared KYC helpers (kyc-proof-types, kyc-upload-helper.service) - User create/edit/view enhancements - Shell navigation: OTP Audit Logs entry under real Admin menu
This commit is contained in:
@@ -72,9 +72,9 @@ export class CreateClientComponent {
|
||||
@ViewChild(ClientGeneralStepComponent) clientGeneralStep: ClientGeneralStepComponent;
|
||||
@ViewChild('clientAddress') clientAddressStep: ClientAddressStepComponent;
|
||||
@ViewChild('clientIndiaAddress') clientIndiaAddressStep: ClientIndiaAddressStepComponent;
|
||||
@ViewChild('clientEntityKyc') clientEntityKycStep: ClientEntityKycStepComponent;
|
||||
@ViewChild(ClientEntityKycStepComponent) clientEntityKycStep: ClientEntityKycStepComponent;
|
||||
@ViewChild('clientKyc') clientKycStep: ClientKycStepComponent;
|
||||
@ViewChild('clientFamilyMembers') clientFamilyMembersStep: ClientFamilyMembersStepComponent;
|
||||
@ViewChild(ClientFamilyMembersStepComponent) clientFamilyMembersStep: ClientFamilyMembersStepComponent;
|
||||
@ViewChildren('dtclient') clientDatatables: QueryList<ClientDatatableStepComponent>;
|
||||
|
||||
datatables: any = [];
|
||||
@@ -250,6 +250,13 @@ export class CreateClientComponent {
|
||||
// KYC tab
|
||||
const kycTabIndex = 1;
|
||||
if (tabIndex === kycTabIndex) {
|
||||
// Photograph + Signature are mandatory before leaving the KYC tab
|
||||
if (this.clientKycStep && !this.clientKycStep.photoSigComplete) {
|
||||
this.clientKycStep.markPhotoSigTouched();
|
||||
this.validationError = 'Photograph and Signature are required before continuing.';
|
||||
return;
|
||||
}
|
||||
this.validationError = '';
|
||||
// Save KYC
|
||||
if (this.clientKycStep?.hasKycData && !this.clientKycStep.kycId) {
|
||||
this.clientsService.saveClientKyc(clientId, this.clientKycStep.kycData).subscribe({ error: () => {} });
|
||||
@@ -265,6 +272,8 @@ export class CreateClientComponent {
|
||||
.uploadClientSignatureImage(clientId, this.clientKycStep.signatureFile)
|
||||
.subscribe({ error: () => {} });
|
||||
}
|
||||
// Persist the KYC document picker selection + uploaded files as a client identifier
|
||||
this.saveKycDocProof(clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -349,6 +358,14 @@ export class CreateClientComponent {
|
||||
validationError = '';
|
||||
|
||||
submit() {
|
||||
// Validate: photograph and signature are mandatory
|
||||
if (this.clientKycStep && !this.clientKycStep.photoSigComplete) {
|
||||
this.clientKycStep.markPhotoSigTouched();
|
||||
this.validationError = 'Photograph and Signature are required before submitting.';
|
||||
// Switch to KYC tab
|
||||
this.selectedTabIndex = 1;
|
||||
return;
|
||||
}
|
||||
// Validate: at least one family member required (Individual clients)
|
||||
if (this.legalFormType === 1 && this.clientFamilyMembersStep && !this.clientFamilyMembersStep.hasFamilyMembers) {
|
||||
this.validationError = 'At least one family member is required. Please add a family member before submitting.';
|
||||
@@ -425,6 +442,9 @@ export class CreateClientComponent {
|
||||
this.saveClientFamilyMembers(clientId);
|
||||
}
|
||||
|
||||
// Persist the KYC document picker selection + uploaded files as a client identifier
|
||||
this.saveKycDocProof(clientId);
|
||||
|
||||
// Save KYC only if it wasn't already created during the KYC step
|
||||
if (this.clientKycStep?.hasKycData && !this.clientKycStep.kycId) {
|
||||
this.clientsService.saveClientKyc(clientId, this.clientKycStep.kycData).subscribe({
|
||||
@@ -514,6 +534,72 @@ export class CreateClientComponent {
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
|
||||
// Persist the selected proof of address as a client identifier (with attached files)
|
||||
this.saveAddressProof(clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the address-proof picked on the permanent tab as a client identifier
|
||||
* + upload each selected file as a document attached to that identifier.
|
||||
* Mirrors identities-tab.createIdentifierWithDocs.
|
||||
*/
|
||||
private saveAddressProof(clientId: string) {
|
||||
const payload = this.clientIndiaAddressStep?.addressProofPayload;
|
||||
if (!payload?.proofId) return;
|
||||
|
||||
const proofName = payload.proof?.name || 'Address Proof';
|
||||
const identifierData: any = {
|
||||
documentTypeId: payload.proofId,
|
||||
status: 'Active',
|
||||
documentKey: payload.documentKey || `${proofName} - ${clientId}`,
|
||||
description: payload.issueDate ? `Issue date: ${payload.issueDate}` : ''
|
||||
};
|
||||
|
||||
this.clientsService.addClientIdentifier(clientId, identifierData).subscribe({
|
||||
next: (res: any) => {
|
||||
const identifierId = res?.resourceId;
|
||||
if (!identifierId) return;
|
||||
for (const entry of payload.files) {
|
||||
const formData = new FormData();
|
||||
formData.append('name', `${proofName} — ${entry.slot}`);
|
||||
formData.append('file', entry.file);
|
||||
this.clientsService.uploadClientIdentifierDocument(identifierId, formData).subscribe({ error: () => {} });
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the KYC document picked on the KYC tab as a client identifier
|
||||
* + upload each selected file as a document attached to that identifier.
|
||||
*/
|
||||
private saveKycDocProof(clientId: string) {
|
||||
const payload = this.clientKycStep?.kycDocPayload;
|
||||
if (!payload?.proofId || !payload?.files?.length) return;
|
||||
|
||||
const proofName = payload.proof?.name || 'KYC Document';
|
||||
const identifierData: any = {
|
||||
documentTypeId: payload.proofId,
|
||||
status: 'Active',
|
||||
documentKey: payload.documentKey || `${proofName} - ${clientId}`,
|
||||
description: 'KYC document'
|
||||
};
|
||||
|
||||
this.clientsService.addClientIdentifier(clientId, identifierData).subscribe({
|
||||
next: (res: any) => {
|
||||
const identifierId = res?.resourceId;
|
||||
if (!identifierId) return;
|
||||
for (const entry of payload.files) {
|
||||
const formData = new FormData();
|
||||
formData.append('name', `${proofName} — ${entry.slot}`);
|
||||
formData.append('file', entry.file);
|
||||
this.clientsService.uploadClientIdentifierDocument(identifierId, formData).subscribe({ error: () => {} });
|
||||
}
|
||||
},
|
||||
error: () => {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user