Client-stepper: pre-fill existing KYC/address proofs in edit, fix identifier saves
Edit-mode now shows the previously selected KYC document type + uploaded file thumbnail
(loads from /clients/{id}/identifiers + /client_identifiers/{id}/documents). Same for
address proof on the India address step. Closes the gap where editing a client showed
an empty file picker even when proof had been uploaded on create.
Identifier-save hardened across all three callsites (create-client, edit-client,
identities-tab.createIdentifierWithDocs) - the KYC chip picker proofId is a CODE
string ('AADHAAR'), but backend ClientIdentifierRequest.documentTypeId is Long, which
Spring rejects with HttpMessageNotReadableException -> 400. resolveDocumentTypeId
helper looks up the code against /kyc/proof-types?type=id and substitutes the numeric
CodeValue id before posting. Skips silently for constitution-specific non-individual
docs that have no CodeValue mapping.
KYC payload normalises panNumber / enterprisePan / gstinNumber / cinNumber via
toUpperCase + whitespace strip so user-typed lowercase or trailing spaces don't trip
the backend regex (validation.msg.CLIENTKYC.panNumber.does.not.match.regexp).
State name match in client-address save now normalises both sides (lowercase, collapse
underscores + spaces) so legacy MADHYA_PRADESH resolves to 'Madhya Pradesh' returned
by api.postalpincode.in. Backed by tenant migration 0297.
Identities-tab resolver flattened - was returning identifiers before the per-row
forkJoin(getClientIdentificationDocuments) completed, so the view rendered empty
documents arrays. Now uses switchMap + forkJoin so the resolver awaits completion.
Also bundles concurrent work-in-progress on letter-mgmt, organization, users,
bank-calendar, shell, nominee/notes/identifier dialogs, and theming.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
325228c514
commit
83ea57ccf0
@@ -44,6 +44,8 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { ClientsService } from '../../clients.service';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* Identities Tab Component
|
||||
@@ -206,15 +208,57 @@ export class IdentitiesTabComponent implements OnDestroy {
|
||||
/**
|
||||
* Create one identifier from a KYC section payload, then upload each file
|
||||
* as a document attached to that identifier.
|
||||
*
|
||||
* `section.proofId` may arrive either as a numeric CodeValue id (when sourced from
|
||||
* /kyc/proof-types — the address-proof chip-picker is built from this list and works
|
||||
* straight away) or as a string CODE like "AADHAAR" / "PAN" (when sourced from the
|
||||
* KYC step's constitution-driven document list — that list isn't backed by
|
||||
* CodeValues). The backend's ClientIdentifierRequest.documentTypeId is a Long, so a
|
||||
* string proofId triggers Spring's HttpMessageNotReadableException → generic 400
|
||||
* "String instead of Number". Route through resolveDocumentTypeId so we always post
|
||||
* a numeric documentTypeId.
|
||||
*/
|
||||
private createIdentifierWithDocs(section: any, description: string, kind: 'id' | 'address'): void {
|
||||
const identifierData = {
|
||||
documentTypeId: section.proofId,
|
||||
status: 'Active',
|
||||
documentKey: kind === 'id' ? section.documentKey : `${section.proof?.name || 'Address Proof'} - ${this.clientId}`,
|
||||
description: kind === 'address' && section.address ? section.address : description
|
||||
};
|
||||
const proofName = section.proof?.name || (kind === 'address' ? 'Address Proof' : 'KYC Document');
|
||||
this.resolveDocumentTypeId(section.proofId, proofName).subscribe((documentTypeId: number | null) => {
|
||||
if (documentTypeId == null) {
|
||||
// No CodeValue match (e.g. a constitution-specific non-individual doc like
|
||||
// "Trust Deed" that hasn't been seeded). Skip silently rather than firing the
|
||||
// 400 — the parent already captured the file separately if needed.
|
||||
return;
|
||||
}
|
||||
const identifierData = {
|
||||
documentTypeId,
|
||||
status: 'Active',
|
||||
documentKey: kind === 'id' ? section.documentKey : `${proofName} - ${this.clientId}`,
|
||||
description: kind === 'address' && section.address ? section.address : description
|
||||
};
|
||||
|
||||
this.postIdentifierAndDocs(identifierData, section, proofName, description);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass-through when proofId is already numeric (address proof, identities tab address
|
||||
* picker). Otherwise resolve the string code/name against the ID-proof CodeValues so
|
||||
* the backend gets a proper Long for documentTypeId.
|
||||
*/
|
||||
private resolveDocumentTypeId(proofId: any, proofName: string): Observable<number | null> {
|
||||
if (typeof proofId === 'number' && Number.isFinite(proofId)) {
|
||||
return of(proofId);
|
||||
}
|
||||
return this.clientService.getKycProofTypes('id').pipe(
|
||||
map((res: any) => {
|
||||
const list: any[] = res?.idProofs || [];
|
||||
const target = (proofName || '').trim().toLowerCase();
|
||||
const match = list.find((p: any) => (p?.name || '').trim().toLowerCase() === target);
|
||||
return (match?.id ?? null) as number | null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/** Extracted post-and-upload-files step so the resolver wrapper stays readable. */
|
||||
private postIdentifierAndDocs(identifierData: any, section: any, proofName: string, description: string): void {
|
||||
this.clientService.addClientIdentifier(this.clientId, identifierData).subscribe({
|
||||
next: (res: any) => {
|
||||
const newIdentifierId = res.resourceId;
|
||||
|
||||
Reference in New Issue
Block a user