feat: KYC re-verify UX, family/nominee tab gating, savings dialogs, cfcfrms shell
- KYC tab: fix Verify (PAN) and Send OTP (Aadhaar) being disabled in edit mode; unmask Aadhaar input and show on-file masked value as hint; resolve kycId from id/kycId/resourceId fallbacks. - Client stepper/view: add familyAllowed / nomineeAllowed predicates + CanActivate route guards. Family tab hidden for non-individuals, Nominee tab hidden for non-proprietor entities. Family-members step accepts a nomineeOnly input for proprietorship clients. Add/Edit/Delete buttons inside family-members and nominee tabs gated by canAdd. - cfcfrms: add cfcfrms-shell wrapper, restyle complaint/fraud/mule/escalate pages and dashboard. - Savings: identity-verify and transaction-confirm dialogs, manage / activate / add-charge flows, account preview styling. - Tasks, core shell, account-nominees, icons: minor style and behaviour updates. 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
bf3e26a0d0
commit
b149fef72e
-7
@@ -57,13 +57,6 @@
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
@if (savingsProductSelected) {
|
||||
<mat-form-field appearance="outline" class="sad-field">
|
||||
<mat-label>{{ 'labels.inputs.External ID' | translate }}</mat-label>
|
||||
<input matInput formControlName="externalId" />
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+47
-4
@@ -82,6 +82,10 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
primaryClientCif = '';
|
||||
primaryClientId: number | null = null;
|
||||
primaryHolderShare = 0;
|
||||
/** Best-effort string label of the client's entity type
|
||||
* (e.g. "Individual", "Corporate", "MSME") — used to auto-fill the
|
||||
* Customer Segment dropdown when the product template lands. */
|
||||
private clientEntityType: string | null = null;
|
||||
|
||||
// ── Status of Account (RBI) ────────────────────────────────────────────
|
||||
readonly accountStatusOptions = [
|
||||
@@ -167,8 +171,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
productId: this.savingsAccountTemplate.savingsProductId,
|
||||
submittedOnDate:
|
||||
this.savingsAccountTemplate.timeline.submittedOnDate &&
|
||||
new Date(this.savingsAccountTemplate.timeline.submittedOnDate),
|
||||
externalId: this.savingsAccountTemplate.externalId
|
||||
new Date(this.savingsAccountTemplate.timeline.submittedOnDate)
|
||||
});
|
||||
} else {
|
||||
this.savingsAccountDetailsForm.patchValue({
|
||||
@@ -193,11 +196,49 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
this.autoDetectAccountStatus();
|
||||
}
|
||||
}
|
||||
this.clientEntityType = this.deriveEntityType(client);
|
||||
// Product template may have already arrived before the client did —
|
||||
// try the segment-apply both here and when the template lands.
|
||||
this.applyInferredCustomerSegment();
|
||||
this.cdr.detectChanges();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a best-effort label for the client's entity / customer type.
|
||||
* Tries `clientType` first (the explicit master), then falls back to
|
||||
* `clientClassification` and finally `legalForm`.
|
||||
*/
|
||||
private deriveEntityType(client: any): string | null {
|
||||
if (!client) return null;
|
||||
const direct =
|
||||
client.clientType?.value ??
|
||||
client.clientType?.name ??
|
||||
client.clientClassification?.value ??
|
||||
client.clientClassification?.name;
|
||||
if (direct && typeof direct === 'string') return direct;
|
||||
const legalForm = client.legalForm?.value ?? client.legalForm?.code ?? client.legalForm?.name;
|
||||
return typeof legalForm === 'string' ? legalForm : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If `clientEntityType` and `customerSegmentOptions` are both ready, try
|
||||
* to match an option (exact then case-insensitive substring) and set it
|
||||
* as the selected Customer Segment. Falls through silently otherwise so
|
||||
* the existing product default stays in place.
|
||||
*/
|
||||
private applyInferredCustomerSegment(): void {
|
||||
if (!this.clientEntityType || !this.customerSegmentOptions?.length) return;
|
||||
const needle = this.clientEntityType.trim().toLowerCase();
|
||||
const match =
|
||||
this.customerSegmentOptions.find((o) => o.toLowerCase() === needle) ||
|
||||
this.customerSegmentOptions.find((o) => o.toLowerCase().includes(needle) || needle.includes(o.toLowerCase()));
|
||||
if (match) {
|
||||
this.selectedCustomerSegment = match;
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse DOB from backend — handles array [yyyy, mm, dd] or string */
|
||||
private parseClientDob(dob: any): Date | null {
|
||||
if (!dob) return null;
|
||||
@@ -263,8 +304,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
fieldOfficerId: [''],
|
||||
externalId: ['']
|
||||
fieldOfficerId: ['']
|
||||
});
|
||||
}
|
||||
|
||||
@@ -279,6 +319,9 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
||||
this.customerSegmentOptions = this.extractOptions(response.customerSegmentOptions, response.customerSegment);
|
||||
this.selectedAccountType = response.accountType ?? null;
|
||||
this.selectedCustomerSegment = response.customerSegment ?? null;
|
||||
// Override the product's default segment with the one inferred
|
||||
// from the client's entity type when an option matches.
|
||||
this.applyInferredCustomerSegment();
|
||||
this.savingsAccountProductTemplate.emit(response);
|
||||
this.fieldOfficerData = response.fieldOfficerOptions;
|
||||
this.savingsProductSelected = true;
|
||||
|
||||
+10
-7
@@ -575,15 +575,18 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button [routerLink]="['../..']">
|
||||
<!--
|
||||
Submit & Cancel — Previous lives in the parent wizard's tab action bar
|
||||
now that this component renders inside a mat-tab-group rather than a
|
||||
mat-stepper. Re-introducing matStepperPrevious here would require a
|
||||
CdkStepper ancestor and would crash the view on init.
|
||||
-->
|
||||
<div class="preview-submit-row">
|
||||
<button mat-stroked-button [routerLink]="['../..']">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary" (click)="submitEvent.emit()">
|
||||
<button mat-raised-button color="primary" class="preview-submit-row__primary" (click)="submitEvent.emit()">
|
||||
<fa-icon icon="check" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
+25
@@ -13,6 +13,31 @@
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
/* Submit + Cancel row at the bottom of the preview. Previous lives in the
|
||||
parent wizard's tab action bar. */
|
||||
.preview-submit-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
|
||||
button {
|
||||
height: 42px !important;
|
||||
border-radius: 10px !important;
|
||||
font-weight: 600 !important;
|
||||
padding: 0 22px !important;
|
||||
}
|
||||
|
||||
&__primary {
|
||||
background: linear-gradient(135deg, #10b981 0%, #047857 100%) !important;
|
||||
color: #fff !important;
|
||||
box-shadow: 0 4px 12px -2px rgb(4 120 87 / 35%) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
|
||||
-2
@@ -23,7 +23,6 @@ import {
|
||||
} from '@angular/material/table';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { ExternalIdentifierComponent } from '../../../shared/external-identifier/external-identifier.component';
|
||||
import { MatStepperPrevious } from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { FindPipe } from '../../../pipes/find.pipe';
|
||||
import { DateFormatPipe } from '../../../pipes/date-format.pipe';
|
||||
@@ -52,7 +51,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
FindPipe,
|
||||
DateFormatPipe,
|
||||
|
||||
Reference in New Issue
Block a user