feat(clients, alm): add CIN/MCA verification & authorized signatories tab; refactor ALM report print and export
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
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

This commit is contained in:
2026-08-13 15:05:16 +05:30
parent 75df8865dd
commit 5581d4180d
29 changed files with 3002 additions and 2453 deletions
@@ -215,11 +215,18 @@
</div>
}
<div class="tab-section">
<!-- constitutionMeta and entityName are bound here exactly as they are in create-client.
Without them an entity opened for edit falls back to the generic document list rather
than the one its constitution actually uses, so the document type saved at creation
can be missing from the picker; a proprietorship is also mis-rendered, since the
proprietor-PAN branch keys off the constitution. -->
<mifosx-client-kyc-step
#clientKyc
[isNonIndividual]="legalFormType === 2"
[isMinor]="clientGeneralStep?.isMinor || false"
[clientId]="clientId"
[constitutionMeta]="clientGeneralStep?.selectedConstitution"
[entityName]="clientGeneralStep?.createClientForm?.get('fullname')?.value || ''"
[verificationName]="clientGeneralStep?.verificationName || ''"
[verificationDateOfBirth]="clientGeneralStep?.verificationDateOfBirth || null"
[readOnly]="isTabReadOnly(1)"
@@ -40,6 +40,7 @@ import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { MatTooltip } from '@angular/material/tooltip';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
import { ORIGINAL_VALUES_PAYLOAD_KEY, buildOriginalValuesSnapshot } from '../shared/client-edit-diff.util';
import { toProperNameCase } from '../utils/name-case.util';
/**
* Edit Client Component — uses the same wizard/stepper layout as Create Client.
@@ -589,13 +590,11 @@ export class EditClientComponent {
const kycId = this.clientKycStep.kycId;
const isVerified = this.clientKycStep.isKycVerified;
// Validate remarks are present for verified KYC
if (isVerified && !kycData.remarks?.trim()) {
this.savingClient = false;
this.clientKycStep.kycForm.get('remarks')?.markAsTouched();
return;
}
// Remarks are not demanded here. The server requires them only when a save actually
// overwrites data the verified record already held, and it names the reason when it
// refuses. Blocking every verified record up front aborted the save silently — the photo
// upload above had already gone through, so the image changed, the button came back to
// life, and nothing said why nothing had been saved.
let saveObs;
if (kycId && isVerified) {
// VERIFIED → re-edit command
@@ -628,8 +627,19 @@ export class EditClientComponent {
this.saveKycDocProof(this.clientId);
this.selectedTabIndex = this.selectedTabIndex + 1;
},
error: () => {
// A rejected save used to just re-enable the button, which reads as "nothing happened".
// The server's own message says what is missing — remarks on a re-edit, most often — so
// it is shown rather than swallowed, and the KYC tab stays put for the officer to fix.
error: (err: any) => {
this.savingClient = false;
const message =
err?.error?.errors?.[0]?.defaultUserMessage ||
err?.error?.defaultUserMessage ||
'KYC could not be saved. Please review the KYC details and try again.';
this.snackBar.open(message, 'Close', { duration: 6000 });
if (isVerified) {
this.clientKycStep?.kycForm.get('remarks')?.markAsTouched();
}
}
});
} else {
@@ -734,17 +744,20 @@ export class EditClientComponent {
? this.toLocalDateString(oldDobRaw)
: oldDobRaw || '';
// Both sides run through toProperNameCase so a casing-only difference — the stored
// 'LINCPAY LTD' against the normalized 'Lincpay LTD' the form now submits — is not
// mistaken for a real rename and does not force a PAN re-verification.
const buildName = (parts: { firstname?: string; middlename?: string; lastname?: string; fullname?: string }) =>
(
toProperNameCase(
parts.fullname ||
[
parts.firstname,
parts.middlename,
parts.lastname
]
.filter((p) => p && p.trim())
.join(' ')
).trim();
[
parts.firstname,
parts.middlename,
parts.lastname
]
.filter((p) => p && p.trim())
.join(' ')
);
const newName = buildName({
firstname: form?.get('firstname')?.value || '',