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
@@ -16,6 +16,9 @@ import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
|
||||
|
||||
/** Custom Components */
|
||||
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
|
||||
import { EditAddressDialogComponent } from './edit-address-dialog/edit-address-dialog.component';
|
||||
import { SuccessDialogComponent } from 'app/shared/success-dialog/success-dialog.component';
|
||||
import { ErrorDialogComponent } from 'app/shared/error-dialog/error-dialog.component';
|
||||
|
||||
/** Custom Services */
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
@@ -87,29 +90,42 @@ export class AddressTabComponent {
|
||||
* Adds a client address.
|
||||
*/
|
||||
addAddress() {
|
||||
const data = {
|
||||
title:
|
||||
this.translateService.instant('labels.buttons.Add') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.catalogs.Client') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.heading.Address'),
|
||||
formfields: this.getAddressFormFields('add'),
|
||||
layout: { columns: 2, columnWidth: 350 }
|
||||
};
|
||||
const addAddressDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
addAddressDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
this.clientService
|
||||
.createClientAddress(this.clientId, response.data.value.addressType, response.data.value)
|
||||
.subscribe((res: any) => {
|
||||
const addressData = response.data.value;
|
||||
addressData.addressId = res.resourceId;
|
||||
addressData.addressType = this.getSelectedValue('addressTypeIdOptions', addressData.addressType).name;
|
||||
addressData.isActive = false;
|
||||
this.clientAddressData.push(addressData);
|
||||
});
|
||||
}
|
||||
// Use the Nexora dialog with PIN lookup + "Copy from existing address" picker.
|
||||
const ref = this.dialog.open(EditAddressDialogComponent, {
|
||||
data: {
|
||||
mode: 'add',
|
||||
title:
|
||||
this.translateService.instant('labels.buttons.Add') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.catalogs.Client') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.heading.Address'),
|
||||
address: null,
|
||||
template: this.clientAddressTemplate,
|
||||
fieldConfig: this.clientAddressFieldConfig,
|
||||
existingAddresses: this.clientAddressData || []
|
||||
},
|
||||
panelClass: 'ead-panel',
|
||||
autoFocus: false
|
||||
});
|
||||
ref.afterClosed().subscribe((response: any) => {
|
||||
if (!response?.value) return;
|
||||
const payload = { ...response.value };
|
||||
const typeName = this.getSelectedValue('addressTypeIdOptions', payload.addressType)?.name;
|
||||
this.clientService.createClientAddress(this.clientId, payload.addressType, payload).subscribe({
|
||||
next: (res: any) => {
|
||||
payload.addressId = res.resourceId;
|
||||
payload.addressType = typeName;
|
||||
payload.isActive = false;
|
||||
this.clientAddressData.push(payload);
|
||||
this.showSuccess('Address Added', `${typeName || 'Address'} has been added to this client's profile.`);
|
||||
},
|
||||
error: (err: any) => {
|
||||
const apiMsg =
|
||||
err?.error?.errors?.[0]?.defaultUserMessage || err?.error?.defaultUserMessage || 'Please try again.';
|
||||
this.showError(`Failed to add address. ${apiMsg}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -119,31 +135,54 @@ export class AddressTabComponent {
|
||||
* @param {number} index address index
|
||||
*/
|
||||
editAddress(address: any, index: number) {
|
||||
const data = {
|
||||
title:
|
||||
this.translateService.instant('labels.buttons.Edit') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.catalogs.Client') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.heading.Address'),
|
||||
formfields: this.getAddressFormFields('edit', address),
|
||||
layout: { columns: 2, columnWidth: 350, addButtonText: 'Edit' }
|
||||
};
|
||||
const editAddressDialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
editAddressDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
const addressData = response.data.value;
|
||||
addressData.addressId = address.addressId;
|
||||
addressData.isActive = address.isActive;
|
||||
this.clientService
|
||||
.editClientAddress(this.clientId, address.addressTypeId, addressData)
|
||||
.subscribe((res: any) => {
|
||||
addressData.addressTypeId = address.addressTypeId;
|
||||
addressData.addressType = address.addressType;
|
||||
this.clientAddressData[index] = addressData;
|
||||
});
|
||||
}
|
||||
// Open the Nexora edit dialog with PIN lookup (mirrors the stepper).
|
||||
const ref = this.dialog.open(EditAddressDialogComponent, {
|
||||
data: {
|
||||
title:
|
||||
this.translateService.instant('labels.buttons.Edit') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.catalogs.Client') +
|
||||
' ' +
|
||||
this.translateService.instant('labels.heading.Address'),
|
||||
address,
|
||||
template: this.clientAddressTemplate,
|
||||
fieldConfig: this.clientAddressFieldConfig
|
||||
},
|
||||
panelClass: 'ead-panel',
|
||||
autoFocus: false
|
||||
});
|
||||
ref.afterClosed().subscribe((response: any) => {
|
||||
if (!response?.value) return;
|
||||
const addressData = { ...response.value };
|
||||
addressData.addressId = address.addressId;
|
||||
addressData.isActive = address.isActive;
|
||||
this.clientService.editClientAddress(this.clientId, address.addressTypeId, addressData).subscribe({
|
||||
next: () => {
|
||||
addressData.addressTypeId = address.addressTypeId;
|
||||
addressData.addressType = address.addressType;
|
||||
this.clientAddressData[index] = addressData;
|
||||
this.showSuccess('Address Updated', `${address.addressType || 'Address'} has been updated successfully.`);
|
||||
},
|
||||
error: (err: any) => {
|
||||
const apiMsg =
|
||||
err?.error?.errors?.[0]?.defaultUserMessage || err?.error?.defaultUserMessage || 'Please try again.';
|
||||
this.showError(`Failed to update address. ${apiMsg}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Polished success modal — matches the rest of the Nexora UI. */
|
||||
private showSuccess(title: string, message: string): void {
|
||||
this.dialog.open(SuccessDialogComponent, {
|
||||
data: { title, message, buttonLabel: 'OK' },
|
||||
panelClass: 'sd-panel'
|
||||
});
|
||||
}
|
||||
|
||||
/** Polished error modal — single string body. */
|
||||
private showError(message: string): void {
|
||||
this.dialog.open(ErrorDialogComponent, { data: message });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,7 +336,9 @@ export class AddressTabComponent {
|
||||
this.isFieldEnabled('countyDistrict')
|
||||
? new InputBase({
|
||||
controlName: 'countyDistrict',
|
||||
label: this.translateService.instant('labels.inputs.State / Province'),
|
||||
// Was mistakenly labeled "State / Province" — that label belongs to
|
||||
// the stateProvinceId select above. This control holds the District.
|
||||
label: this.translateService.instant('labels.inputs.County / District'),
|
||||
value: address ? address.countyDistrict : '',
|
||||
type: 'text',
|
||||
order: 11
|
||||
|
||||
Reference in New Issue
Block a user