Files
cbsnexora/src/app/clients/clients-view/address-tab/edit-address-dialog/edit-address-dialog.component.html
T
AdityaandClaude Opus 4.7 83ea57ccf0 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>
2026-05-20 12:40:58 +05:30

187 lines
6.6 KiB
HTML

<!--
Copyright since 2025 Mifos Initiative
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
-->
<div class="ead-root">
<div class="ead-head">
<div class="ead-head__left">
<fa-icon icon="map-marker-alt" class="ead-head__ic"></fa-icon>
<div>
<h2 class="ead-head__title">{{ data.title || (isAdd ? 'Add Client Address' : 'Edit Client Address') }}</h2>
<div class="ead-head__sub">
@if (isAdd) {
Select address type and enter PIN to auto-fill city, district &amp; state.
} @else {
Update postal details. Enter PIN to auto-fill city, district &amp; state.
}
</div>
</div>
</div>
<button class="ead-x" (click)="cancel()" matTooltip="Close">
<fa-icon icon="times"></fa-icon>
</button>
</div>
<form class="ead-body" [formGroup]="form" (ngSubmit)="submit()">
<!-- Address Type + Copy-from (Add mode only) -->
@if (isAdd) {
<div class="ead-grid">
<div class="ead-field">
<label>Address Type *</label>
<select formControlName="addressType">
<option [ngValue]="''">— Select address type —</option>
@for (t of addressTypeOptions; track t.id) {
<option [ngValue]="t.id">{{ t.name }}</option>
}
</select>
@if (form.get('addressType')?.touched && form.get('addressType')?.hasError('required')) {
<div class="ead-err">Address type is required.</div>
}
</div>
@if (copySourceOptions.length > 0) {
<div class="ead-field">
<label>Copy from existing address (optional)</label>
<select
[(ngModel)]="copyFromAddressId"
[ngModelOptions]="{ standalone: true }"
(change)="onCopyFromAddress()"
>
<option [ngValue]="''">— Don't copy —</option>
@for (a of copySourceOptions; track a.addressId) {
<option [ngValue]="a.addressId">
{{ a.addressType || 'Address' }} &middot; {{ a.city || a.countyDistrict || '' }}
@if (a.postalCode) {
&middot; {{ a.postalCode }}
}
</option>
}
</select>
</div>
}
</div>
}
<!-- PIN + Street -->
<div class="ead-grid">
<div class="ead-field">
<label>Postal Code *</label>
<div class="ead-input-wrap">
<fa-icon icon="map-pin" class="ead-input-ic"></fa-icon>
<input
type="text"
inputmode="numeric"
maxlength="6"
formControlName="postalCode"
placeholder="6-digit PIN"
(input)="onPinInput()"
(keypress)="$event.charCode >= 48 && $event.charCode <= 57 ? true : $event.preventDefault()"
/>
@if (pinStatus.loading) {
<span class="ead-pin-status loading"
><fa-icon icon="spinner" class="fa-spin"></fa-icon> Looking up&hellip;</span
>
} @else if (pinStatus.found) {
<span class="ead-pin-status ok"><fa-icon icon="check-circle"></fa-icon> PIN verified</span>
} @else if (pinStatus.error) {
<span class="ead-pin-status err"
><fa-icon icon="exclamation-triangle"></fa-icon> {{ pinStatus.message }}</span
>
}
</div>
@if (form.get('postalCode')?.touched && form.get('postalCode')?.hasError('pattern')) {
<div class="ead-err">Enter a valid 6-digit PIN.</div>
}
</div>
@if (isFieldEnabled('street')) {
<div class="ead-field">
<label>Street</label>
<input type="text" formControlName="street" placeholder="Street, Road, Lane" />
</div>
}
</div>
<!-- Address lines -->
<div class="ead-grid">
@if (isFieldEnabled('addressLine1')) {
<div class="ead-field">
<label>Address Line 1</label>
<input type="text" formControlName="addressLine1" placeholder="House / Building No." />
</div>
}
@if (isFieldEnabled('addressLine2')) {
<div class="ead-field">
<label>Address Line 2</label>
<input type="text" formControlName="addressLine2" placeholder="Apartment / Block" />
</div>
}
</div>
<div class="ead-grid">
@if (isFieldEnabled('addressLine3')) {
<div class="ead-field">
<label>Address Line 3</label>
<input type="text" formControlName="addressLine3" placeholder="Locality / Sector" />
</div>
}
@if (isFieldEnabled('townVillage')) {
<div class="ead-field">
<label>Town / Village (Locality)</label>
<select formControlName="townVillage">
<option [ngValue]="''">— Select —</option>
@for (loc of effectiveLocalityOptions; track loc) {
<option [ngValue]="loc">{{ loc }}</option>
}
</select>
</div>
}
</div>
<!-- City / District / State -->
<div class="ead-grid ead-grid--three">
@if (isFieldEnabled('city')) {
<div class="ead-field">
<label>City</label>
<input type="text" formControlName="city" placeholder="City" />
</div>
}
@if (isFieldEnabled('countyDistrict')) {
<div class="ead-field">
<label>County / District</label>
<input type="text" formControlName="countyDistrict" placeholder="District" />
</div>
}
@if (isFieldEnabled('stateProvinceId')) {
<div class="ead-field">
<label>State / Province</label>
<!-- Plain text — populated by the PIN service, mirrors create wizard.
On submit we resolve to the matching stateProvinceId for the API. -->
<input type="text" formControlName="state" placeholder="State / Province" />
</div>
}
</div>
<!-- Country -->
@if (isFieldEnabled('countryId')) {
<div class="ead-grid">
<div class="ead-field">
<label>Country</label>
<input type="text" formControlName="country" placeholder="Country" />
</div>
<div></div>
</div>
}
<div class="ead-actions">
<button type="button" class="ead-btn ghost" (click)="cancel()">Cancel</button>
<button type="submit" class="ead-btn primary" [disabled]="form.invalid">
<fa-icon [icon]="isAdd ? 'plus' : 'check'"></fa-icon>
{{ isAdd ? 'Add Address' : 'Save Changes' }}
</button>
</div>
</form>
</div>