feat(savings,deposits,current-accounts): streamline account activation flow

Matches the backend change that stopped auto-depositing money at
activation: removes the opening-balance field from every Activate
screen (Savings, Current, FD, RD) since funding now happens as a
separate deposit afterward, and removes the Digital-Facility step from
every Create wizard since every facility starts disabled by default
and gets turned on later through Edit Nominee & Features.

Also merges Approve+Activate into a single action for Savings,
Current, FD and RD accounts -- one click takes the account straight to
Active instead of two separate screens.
This commit is contained in:
2026-08-19 10:39:52 +05:30
parent c18219fce5
commit ff781e50e3
24 changed files with 85 additions and 156 deletions
@@ -12,9 +12,7 @@
<fa-icon icon="university" class="cca-header__icon"></fa-icon>
<div>
<h2 class="cca-header__title">Create Current Account</h2>
<div class="cca-header__sub">
Pick a product, confirm currency, set digital channels and review before submit.
</div>
<div class="cca-header__sub">Pick a product, confirm currency and review before submit.</div>
</div>
</div>
@@ -78,34 +76,7 @@
</div>
</mat-tab>
<!-- Tab 3: Digital -->
<mat-tab [disabled]="!currentAccountProductTemplate">
<ng-template mat-tab-label>
<fa-icon icon="mobile-alt" class="cca-tab__icon"></fa-icon>
Digital
</ng-template>
<div class="cca-tab-body">
@if (currentAccountProductTemplate) {
<mifosx-current-account-digital-step
[currentAccountProductTemplate]="currentAccountProductTemplate"
></mifosx-current-account-digital-step>
}
<div class="cca-actions">
<button mat-stroked-button (click)="prev()">
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<span class="cca-actions__spacer"></span>
<button mat-raised-button color="primary" (click)="next()">
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
</div>
</div>
</mat-tab>
<!-- Tab 4: Preview -->
<!-- Tab 3: Preview -->
<mat-tab [disabled]="!currentAccountProductTemplate">
<ng-template mat-tab-label>
<fa-icon icon="eye" class="cca-tab__icon"></fa-icon>
@@ -57,8 +57,8 @@ export class CreateCurrentAccountComponent {
currentAccountProductTemplate: any;
/** Currently visible wizard tab (0-indexed). */
selectedTabIndex = 0;
/** Total wizard tabs — Product, Currency, Digital, Preview. */
private readonly totalTabs = 4;
/** Total wizard tabs — Product, Currency, Preview. */
private readonly totalTabs = 3;
@ViewChild(CurrentAccountDetailsStepComponent, { static: true })
currentAccountDetailsStep: CurrentAccountDetailsStepComponent;
@@ -30,18 +30,6 @@
</mat-error>
}
</mat-form-field>
<mat-form-field class="flex-fill">
<mat-label>Opening Balance</mat-label>
<input matInput type="number" [min]="minRequiredOpeningBalance ?? 0" formControlName="openingBalance" />
<span matPrefix>₹&nbsp;</span>
@if (minRequiredOpeningBalance) {
<mat-hint>Minimum required: ₹{{ minRequiredOpeningBalance }}</mat-hint>
}
@if (activateCurrentAccountForm.controls.openingBalance.hasError('min')) {
<mat-error>Opening balance must be at least ₹{{ minRequiredOpeningBalance ?? 0 }}</mat-error>
}
</mat-form-field>
</div>
</mat-card-content>
@@ -7,7 +7,7 @@
*/
/** Angular Imports */
import { Component, OnInit, Input, inject } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Dates } from 'app/core/utils/dates';
@@ -36,8 +36,6 @@ export class ActivateCurrentAccountComponent implements OnInit {
private router = inject(Router);
private settingsService = inject(SettingsService);
@Input() minRequiredOpeningBalance: number | null = null;
/** Minimum date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum date allowed. */
@@ -71,18 +69,10 @@ export class ActivateCurrentAccountComponent implements OnInit {
* Creates the activate current account form.
*/
createActivateCurrentAccountForm() {
const minBalance = this.minRequiredOpeningBalance ?? 0;
this.activateCurrentAccountForm = this.formBuilder.group({
activatedOnDate: [
'',
Validators.required
],
openingBalance: [
minBalance,
[
Validators.required,
Validators.min(minBalance)
]
]
});
}
@@ -10,6 +10,7 @@
<mat-card>
<form [formGroup]="approveCurrentAccountForm" (ngSubmit)="submit()">
<mat-card-content>
<p class="mat-body-2">This approves and activates the account in one step -- it goes straight to Active.</p>
<div class="layout-column">
<mat-form-field (click)="approvedOnDatePicker.open()">
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
@@ -81,8 +81,9 @@ export class ApproveCurrentAccountComponent implements OnInit {
}
/**
* Submits the form and approves the current account,
* if successful redirects to the current account.
* Submits the form, approves the current account, then immediately activates it on the same
* date -- Approve and Activate are still two separate Fineract commands underneath (each shows
* up in the audit trail), but the officer only sees and clicks through one step.
*/
submit() {
const approveCurrentAccountFormData = this.approveCurrentAccountForm.value;
@@ -98,7 +99,16 @@ export class ApproveCurrentAccountComponent implements OnInit {
locale
};
this.currentAccountsService.executeCurrentAccountCommand(this.accountId, 'approve', data).subscribe(() => {
this.router.navigate(['../../transactions'], { relativeTo: this.route });
const activationData = {
activatedOnDate: approveCurrentAccountFormData.approvedOnDate,
dateFormat,
locale
};
this.currentAccountsService
.executeCurrentAccountCommand(this.accountId, 'activate', activationData)
.subscribe(() => {
this.router.navigate(['../../transactions'], { relativeTo: this.route });
});
});
}
}
@@ -13,9 +13,7 @@
<mifosx-reject-current-account></mifosx-reject-current-account>
}
@if (actions['Activate']) {
<mifosx-activate-current-account
[minRequiredOpeningBalance]="accountData?.minRequiredOpeningBalance"
></mifosx-activate-current-account>
<mifosx-activate-current-account></mifosx-activate-current-account>
}
@if (actions['Undo Approval']) {
<mifosx-undo-approval-current-account></mifosx-undo-approval-current-account>
@@ -15,7 +15,6 @@
<h3 class="dig-header__title">Digital &amp; Account Features</h3>
<p class="dig-header__sub">Toggle the facilities available for this account (as per RBI guidelines)</p>
</div>
<span class="dig-header__badge">FROM PRODUCT</span>
</div>
<div class="dig-grid">
@@ -52,12 +52,11 @@ export class CurrentAccountDigitalStepComponent {
}
}
// Note: a new account always starts with every digital facility off, even if the product has
// its own defaults configured -- those are enabled deliberately, one at a time, after the
// account is created and active, not inherited automatically at opening.
@Input()
set currentAccountProductTemplate(value: any) {
if (!this.hasExistingAccount && value?.accountFacilityConfig) {
this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
}
}
currentAccountProductTemplate: any;
get currentAccountDigital() {
return { accountFacilityConfig: { ...this.facilityConfig } };
@@ -136,12 +136,11 @@ export class CurrentAccountPreviewStepComponent implements OnChanges {
return this.currentAccountSettingsForm?.getRawValue?.() || this.currentAccount || {};
}
/** Digital facility config as actually captured by a digital step in this wizard -- no fallback
* to the product's own defaults, since account creation no longer has a digital step at all and
* must not preview facilities the officer never actually chose. */
get digitalFacilityConfig(): any {
return (
this.currentAccountDigital?.accountFacilityConfig ??
this.currentAccountProductTemplate?.accountFacilityConfig ??
null
);
return this.currentAccountDigital?.accountFacilityConfig ?? null;
}
get hasDigitalSection(): boolean {
@@ -10,6 +10,7 @@
<mat-card>
<form [formGroup]="approveFixedDepositsAccountForm" (ngSubmit)="submit()">
<mat-card-content>
<p class="mat-body-2">This approves and activates the account in one step -- it goes straight to Active.</p>
<div class="layout-column">
<mat-form-field (click)="approvedOnDatePicker.open()">
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
@@ -81,8 +81,9 @@ export class ApproveFixedDepositsAccountComponent implements OnInit {
}
/**
* Submits the form and approves the fixed deposit account,
* if successful redirects to the fixed deposit account.
* Submits the form, approves the fixed deposit account, then immediately activates it on the
* same date -- Approve and Activate are still two separate Fineract commands underneath (each
* shows up in the audit trail), but the officer only sees and clicks through one step.
*/
submit() {
const approveFixedDepositsAccountFormData = this.approveFixedDepositsAccountForm.value;
@@ -98,7 +99,16 @@ export class ApproveFixedDepositsAccountComponent implements OnInit {
locale
};
this.fixedDepositsService.executeFixedDepositsAccountCommand(this.accountId, 'approve', data).subscribe(() => {
this.router.navigate(['../../'], { relativeTo: this.route });
const activationData = {
activatedOnDate: approveFixedDepositsAccountFormData.approvedOnDate,
dateFormat,
locale
};
this.fixedDepositsService
.executeFixedDepositsAccountCommand(this.accountId, 'activate', activationData)
.subscribe(() => {
this.router.navigate(['../../'], { relativeTo: this.route });
});
});
}
}
@@ -10,6 +10,7 @@
<mat-card>
<form [formGroup]="approveRecurringDepositsAccountForm" (ngSubmit)="submit()">
<mat-card-content>
<p class="mat-body-2">This approves and activates the account in one step -- it goes straight to Active.</p>
<div class="layout-column">
<mat-form-field (click)="approvedOnDatePicker.open()">
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
@@ -81,8 +81,9 @@ export class ApproveRecurringDepositsAccountComponent implements OnInit {
}
/**
* Submits the form and approves the recurring deposit account,
* if successful redirects to the recurring deposit account.
* Submits the form, approves the recurring deposit account, then immediately activates it on
* the same date -- Approve and Activate are still two separate Fineract commands underneath
* (each shows up in the audit trail), but the officer only sees and clicks through one step.
*/
submit() {
const approveRecurringDepositsAccountFormData = this.approveRecurringDepositsAccountForm.value;
@@ -103,7 +104,16 @@ export class ApproveRecurringDepositsAccountComponent implements OnInit {
this.recurringDepositsService
.executeRecurringDepositsAccountCommand(this.accountId, 'approve', data)
.subscribe(() => {
this.router.navigate(['../../'], { relativeTo: this.route });
const activationData = {
activatedOnDate: approveRecurringDepositsAccountFormData.approvedOnDate,
dateFormat,
locale
};
this.recurringDepositsService
.executeRecurringDepositsAccountCommand(this.accountId, 'activate', activationData)
.subscribe(() => {
this.router.navigate(['../../'], { relativeTo: this.route });
});
});
}
}
@@ -12,9 +12,7 @@
<fa-icon icon="piggy-bank" class="csa-header__icon"></fa-icon>
<div>
<h2 class="csa-header__title">Create Savings Account</h2>
<div class="csa-header__sub">
Pick a product, confirm currency, set digital channels and review before submit.
</div>
<div class="csa-header__sub">Pick a product, confirm currency and review before submit.</div>
</div>
</div>
@@ -76,33 +74,7 @@
</div>
</mat-tab>
<!-- Tab 3: Digital -->
<mat-tab [disabled]="!savingsAccountProductTemplate">
<ng-template mat-tab-label>
<fa-icon icon="mobile-alt" class="csa-tab__icon"></fa-icon>
Digital
</ng-template>
<div class="csa-tab-body">
@if (savingsAccountProductTemplate) {
<mifosx-savings-account-digital-step [savingsAccountProductTemplate]="savingsAccountProductTemplate">
</mifosx-savings-account-digital-step>
}
<div class="csa-actions">
<button mat-stroked-button (click)="prev()">
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
{{ 'labels.buttons.Previous' | translate }}
</button>
<span class="csa-actions__spacer"></span>
<button mat-raised-button color="primary" (click)="next()">
{{ 'labels.buttons.Next' | translate }}
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
</button>
</div>
</div>
</mat-tab>
<!-- Tab 4: Preview / Submit -->
<!-- Tab 3: Preview / Submit -->
<mat-tab [disabled]="!savingsAccountProductTemplate">
<ng-template mat-tab-label>
<fa-icon icon="eye" class="csa-tab__icon"></fa-icon>
@@ -62,8 +62,8 @@ export class CreateSavingsAccountComponent {
savingsAccountProductTemplate: any;
/** Currently visible wizard tab (0-indexed). */
selectedTabIndex = 0;
/** Total wizard tabs — Product, Currency, Digital, Preview. */
private readonly totalTabs = 4;
/** Total wizard tabs — Product, Currency, Preview. */
private readonly totalTabs = 3;
/** Guards against duplicate submissions from a double-click while the create request is in flight. */
submitting = false;
@@ -14,7 +14,9 @@
</div>
<div>
<h2 class="act-savings__title">Activate Savings Account</h2>
<p class="act-savings__sub">Set the activation date and opening balance to bring this account live.</p>
<p class="act-savings__sub">
Set the activation date to bring this account live -- funding happens afterwards, as a separate deposit.
</p>
</div>
</div>
@@ -45,18 +47,6 @@
</mat-error>
}
</mat-form-field>
<mat-form-field appearance="outline" class="act-savings__field">
<mat-label>Opening Balance</mat-label>
<input matInput type="number" [min]="minRequiredOpeningBalance ?? 0" formControlName="openingBalance" />
<span matPrefix class="act-savings__prefix">₹&nbsp;</span>
@if (minRequiredOpeningBalance) {
<mat-hint>Minimum required: ₹{{ minRequiredOpeningBalance }}</mat-hint>
}
@if (activateSavingsAccountForm.controls.openingBalance.hasError('min')) {
<mat-error>Opening balance must be at least ₹{{ minRequiredOpeningBalance ?? 0 }}</mat-error>
}
</mat-form-field>
</div>
<div class="act-savings__actions">
@@ -7,7 +7,7 @@
*/
/** Angular Imports */
import { Component, OnInit, Input, inject } from '@angular/core';
import { Component, OnInit, inject } from '@angular/core';
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { Dates } from 'app/core/utils/dates';
@@ -41,8 +41,6 @@ export class ActivateSavingsAccountComponent implements OnInit {
private router = inject(Router);
private settingsService = inject(SettingsService);
@Input() minRequiredOpeningBalance: number | null = null;
/** Minimum date allowed. */
minDate = new Date(2000, 0, 1);
/** Maximum date allowed. */
@@ -76,18 +74,10 @@ export class ActivateSavingsAccountComponent implements OnInit {
* Creates the activate savings account form.
*/
createActivateSavingsAccountForm() {
const minBalance = this.minRequiredOpeningBalance ?? 0;
this.activateSavingsAccountForm = this.formBuilder.group({
activatedOnDate: [
'',
Validators.required
],
openingBalance: [
minBalance,
[
Validators.required,
Validators.min(minBalance)
]
]
});
}
@@ -109,9 +99,6 @@ export class ActivateSavingsAccountComponent implements OnInit {
dateFormat,
locale
};
if (data.openingBalance === 0 || data.openingBalance === null) {
delete data.openingBalance;
}
this.savingsService.executeSavingsAccountCommand(this.accountId, 'activate', data).subscribe(() => {
this.router.navigate(['../../transactions'], { relativeTo: this.route });
});
@@ -10,6 +10,7 @@
<mat-card>
<form [formGroup]="approveSavingsAccountForm" (ngSubmit)="submit()">
<mat-card-content>
<p class="mat-body-2">This approves and activates the account in one step -- it goes straight to Active.</p>
<div class="layout-column">
<mat-form-field (click)="approvedOnDatePicker.open()">
<mat-label>{{ 'labels.inputs.Approved On Date' | translate }}</mat-label>
@@ -81,8 +81,9 @@ export class ApproveSavingsAccountComponent implements OnInit {
}
/**
* Submits the form and approves the saving account,
* if successful redirects to the saving account.
* Submits the form, approves the saving account, then immediately activates it on the same
* date -- Approve and Activate are still two separate Fineract commands underneath (each shows
* up in the audit trail), but the officer only sees and clicks through one step.
*/
submit() {
const approveSavingsAccountFormData = this.approveSavingsAccountForm.value;
@@ -98,7 +99,14 @@ export class ApproveSavingsAccountComponent implements OnInit {
locale
};
this.savingsService.executeSavingsAccountCommand(this.accountId, 'approve', data).subscribe(() => {
this.router.navigate(['../../transactions'], { relativeTo: this.route });
const activationData = {
activatedOnDate: approveSavingsAccountFormData.approvedOnDate,
dateFormat,
locale
};
this.savingsService.executeSavingsAccountCommand(this.accountId, 'activate', activationData).subscribe(() => {
this.router.navigate(['../../transactions'], { relativeTo: this.route });
});
});
}
}
@@ -13,9 +13,7 @@
<mifosx-reject-savings-account></mifosx-reject-savings-account>
}
@if (actions['Activate']) {
<mifosx-activate-savings-account
[minRequiredOpeningBalance]="accountData?.minRequiredOpeningBalance"
></mifosx-activate-savings-account>
<mifosx-activate-savings-account></mifosx-activate-savings-account>
}
@if (actions['Undo Approval']) {
<mifosx-undo-approval-savings-account></mifosx-undo-approval-savings-account>
@@ -15,7 +15,6 @@
<h3 class="dig-header__title">Digital &amp; Account Features</h3>
<p class="dig-header__sub">Toggle the facilities available for this account (as per RBI guidelines)</p>
</div>
<span class="dig-header__badge">FROM PRODUCT</span>
</div>
<div class="dig-grid">
@@ -50,12 +50,11 @@ export class SavingsAccountDigitalStepComponent {
}
}
// Note: a new account always starts with every digital facility off, even if the product has
// its own defaults configured -- those are enabled deliberately, one at a time, after the
// account is created and active, not inherited automatically at opening.
@Input()
set savingsAccountProductTemplate(value: any) {
if (!this.hasExistingAccount && value?.accountFacilityConfig) {
this.facilityConfig = { ...DEFAULT_FACILITY_CONFIG, ...value.accountFacilityConfig };
}
}
savingsAccountProductTemplate: any;
get savingsAccountDigital() {
return { accountFacilityConfig: { ...this.facilityConfig } };
@@ -124,13 +124,11 @@ export class SavingsAccountPreviewStepComponent implements OnChanges {
}
}
/** Returns user-modified digital config (from digital step), falling back to product template */
/** Digital facility config as actually captured by a digital step in this wizard -- no fallback
* to the product's own defaults, since account creation no longer has a digital step at all and
* must not preview facilities the officer never actually chose. */
get digitalFacilityConfig(): any {
return (
this.savingsAccountDigital?.accountFacilityConfig ??
this.savingsAccountProductTemplate?.accountFacilityConfig ??
null
);
return this.savingsAccountDigital?.accountFacilityConfig ?? null;
}
get hasDigitalSection(): boolean {