feat: add nominee, auto-debit, charges UI for SB/CA/FD/RD accounts; enforce required fields

- SB/CA/FD/RD Currency Step: add Nomination Facility toggle with multi-nominee form
  (NomineeFormComponent), auto-debit facility toggle, dormancy alert panel
- SB/CA/FD/RD Preview Step: display nominees list, auto-debit status, dormancy info
- FD/RD create component: validate Deposit Tenure, Monthly Installment (RD), and
  Customer Segment are filled before submit; show clear alert messages on missing fields
- FD account details step: remove duplicate Currency section (already shown in
  Currency & Account Status Configuration step)
- CA/SB create component: handle duplicate externalId API error with user-friendly alert
  instead of raw error display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nitesh Yadav
2026-05-20 08:08:08 +05:30
co-authored by Claude Sonnet 4.6
parent 37ac15f42c
commit 3df7d100df
29 changed files with 1658 additions and 443 deletions
@@ -95,7 +95,7 @@
[savingsAccountTermsForm]="savingsAccountTermsForm"
[savingsAccount]="savingsAccount"
[savingsAccountDigital]="savingsAccountDigital"
[savingsAccountNominee]="savingsAccountNominee"
[savingsAccountNominees]="savingsAccountCurrencyStep?.nominees ?? []"
(submitEvent)="submit()"
>
</mifosx-savings-account-preview-step>
@@ -107,10 +107,6 @@ export class CreateSavingsAccountComponent {
return this.savingsAccountDigitalStep?.savingsAccountDigital ?? null;
}
get savingsAccountNominee() {
return this.savingsAccountCurrencyStep?.liveNomineeData ?? null;
}
submit() {
const detailsStep = this.savingsAccountDetailsStep;
if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
@@ -120,6 +116,23 @@ export class CreateSavingsAccountComponent {
});
return;
}
if (
this.savingsAccountProductTemplate?.nominationFacility &&
(this.savingsAccountCurrencyStep?.nominees ?? []).length === 0
) {
this.alertService.alert({
type: 'Error',
message: 'Nomination Facility is enabled for this product. Please add at least one nominee before submitting.'
});
return;
}
if (this.savingsAccountProductTemplate?.introducerRequired && !this.savingsAccountDetailsStep.introducerId) {
this.alertService.alert({
type: 'Error',
message: 'Introducer is required for this product. Please select an introducer before submitting.'
});
return;
}
const locale = this.settingsService.language.code;
const dateFormat = this.settingsService.dateFormat;
const monthDayFormat = 'dd MMMM';
@@ -143,27 +156,45 @@ export class CreateSavingsAccountComponent {
savingsAccount.groupId = this.savingsAccountTemplate.groupId;
}
const nominee = this.savingsAccountNominee;
if (nominee) {
savingsAccount.nominee = {
nomineeClientId: nominee.nomineeClientId,
nominationType: nominee.nominationType,
relationshipId: nominee.relationshipId,
percentageShare: nominee.percentageShare,
nomineeIsMinor: nominee.nomineeIsMinor,
...(nominee.guardianClientId ? { guardianClientId: nominee.guardianClientId } : {}),
remarks: nominee.remarks
};
const nominees = this.savingsAccountCurrencyStep?.nominees ?? [];
if (nominees.length > 0) {
savingsAccount.nominees = nominees.map((n: any) => ({
nomineeClientId: n.nomineeClientId,
nominationType: n.nominationType,
relationshipId: n.relationshipId,
percentageShare: n.percentageShare,
nomineeIsMinor: n.nomineeIsMinor,
...(n.guardianClientId ? { guardianClientId: n.guardianClientId } : {}),
remarks: n.remarks
}));
}
this.savingsService.createSavingsAccount(savingsAccount).subscribe((response: any) => {
this.router.navigate(
[
'../',
response.resourceId
],
{ relativeTo: this.route }
);
this.savingsService.createSavingsAccount(savingsAccount).subscribe({
next: (response: any) => {
this.router.navigate(
[
'../',
response.resourceId
],
{ relativeTo: this.route }
);
},
error: (error: any) => {
const errorCode =
error?.error?.errors?.[0]?.userMessageGlobalisationCode ?? error?.error?.userMessageGlobalisationCode;
if (errorCode === 'error.msg.savings.account.duplicate.product.for.client') {
this.alertService.alert({
type: 'Error',
message:
'This client already has an active account with the selected product. Please choose a different product.'
});
} else if (errorCode === 'error.msg.savingsaccount.duplicate.externalId') {
this.alertService.alert({
type: 'Error',
message: 'An account with this External ID already exists. Please use a unique External ID.'
});
}
}
});
}
}