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>
187 lines
6.6 KiB
HTML
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 & state.
|
|
} @else {
|
|
Update postal details. Enter PIN to auto-fill city, district & 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' }} · {{ a.city || a.countyDistrict || '' }}
|
|
@if (a.postalCode) {
|
|
· {{ 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…</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>
|