Actions
-
- Compare
+
+ {{ loadingCompareFor === edit ? 'Loading…' : 'Compare' }}
Approve
@@ -84,67 +89,6 @@
-
- @if (selectedEdit) {
-
-
-
- @if (selectedEdit.facilityConfigSubmitted) {
-
-
Digital & Account Features
-
-
-
- Feature
- Proposed Value
-
-
-
- @for (row of entries(selectedEdit.proposedFacilityConfig); track row.key) {
-
- {{ row.key }}
- {{ row.value }}
-
- }
-
-
-
- }
-
- @if (selectedEdit.nomineeSubmitted) {
-
-
Nominee Change
-
-
-
- Field
- Proposed Value
-
-
-
- @for (row of entries(selectedEdit.proposedNominee); track row.key) {
-
- {{ row.key }}
- {{ row.value }}
-
- }
-
-
-
- }
-
- @if (!selectedEdit.facilityConfigSubmitted && !selectedEdit.nomineeSubmitted) {
-
No proposed changes recorded on this edit.
- }
-
- @if (selectedEdit.rejectReason) {
-
Reject Reason: {{ selectedEdit.rejectReason }}
- }
-
- }
}
@if (!showData) {
diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.scss
index 81f4fff23..8645ecba1 100644
--- a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.scss
+++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.scss
@@ -107,79 +107,6 @@ $text-muted: #64748b;
}
}
-.psae-diff-panel {
- margin-top: 20px;
- border: 1px solid $border;
- border-radius: 12px;
- background: #fff;
- padding: 16px;
-}
-
-.psae-diff-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
-
- h3 {
- margin: 0;
- font-size: 14px;
- font-weight: 700;
- }
-}
-
-.psae-diff-block {
- margin-top: 16px;
-
- h4 {
- margin: 0 0 6px;
- font-size: 12.5px;
- font-weight: 700;
- color: #0d1b2a;
- text-transform: uppercase;
- letter-spacing: 0.03em;
- }
-}
-
-.psae-diff-loading {
- color: $text-muted;
- font-size: 13px;
-}
-
-.psae-diff-table {
- width: 100%;
- border-collapse: collapse;
- margin-top: 4px;
- font-size: 12.5px;
-
- th,
- td {
- text-align: left;
- padding: 8px 10px;
- border-bottom: 1px solid $border;
- }
-
- th {
- color: $text-muted;
- font-weight: 600;
- }
-}
-
-.psae-diff-label {
- font-weight: 600;
- color: #0d1b2a;
-}
-
-.psae-diff-proposed {
- color: #15803d;
- font-weight: 600;
-}
-
-.psae-reject-reason {
- margin-top: 14px;
- font-size: 12.5px;
- color: #b91c1c;
-}
-
.empty-state {
display: flex;
flex-direction: column;
diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.ts
index 78bd3c1fa..e1f307f0c 100644
--- a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.ts
+++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.ts
@@ -32,6 +32,7 @@ import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/conf
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base';
+import { SavingsAccountEditCompareDialogComponent } from './savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
/**
@@ -79,7 +80,8 @@ export class PendingSavingsAccountEditsComponent {
'actions'
];
- selectedEdit: any = null;
+ /** Set only while the current account is being fetched, so the button can show progress. */
+ loadingCompareFor: any = null;
constructor() {
this.route.data.subscribe((data: { pendingSavingsAccountEditsData: any; pendingCurrentAccountEditsData: any }) => {
@@ -120,18 +122,37 @@ export class PendingSavingsAccountEditsComponent {
return isNaN(d.getTime()) ? String(date) : d.toLocaleString('en-IN');
}
- /** Readable list of "key: value" pairs for the proposed facility/nominee changes. */
- entries(obj: any): { key: string; value: any }[] {
- if (!obj) return [];
- return Object.keys(obj).map((key) => ({ key, value: obj[key] }));
- }
-
+ /**
+ * Fetches the account's current Digital & Account Feature config so the popup can show it next to
+ * what's proposed, then opens the compare dialog. The maker's edit form already submits only the
+ * toggles it actually changed (see EditSavingsAccountFeaturesComponent#buildFacilityDiff), so
+ * proposedFacilityConfig only ever carries changed fields -- the dialog still diffs against this
+ * current config defensively, so an unchanged value can never appear in the popup.
+ */
viewCompare(edit: any): void {
- this.selectedEdit = this.selectedEdit === edit ? null : edit;
- }
-
- closeCompare(): void {
- this.selectedEdit = null;
+ this.loadingCompareFor = edit;
+ const isCurrent = edit.accountType === 'Current';
+ const account$ = isCurrent
+ ? this.currentAccountsService.getCurrentAccountAndTemplate(edit.currentAccountId, false)
+ : this.savingsService.getSavingsAccountAndTemplate(edit.savingsAccountId, false);
+ account$.subscribe({
+ next: (account: any) => {
+ this.loadingCompareFor = null;
+ this.dialog.open(SavingsAccountEditCompareDialogComponent, {
+ width: '760px',
+ data: {
+ accountNo: edit.accountNo,
+ currentFacilityConfig: account?.accountFacilityConfig ?? {},
+ proposedFacilityConfig: edit.proposedFacilityConfig ?? {},
+ proposedNominee: edit.proposedNominee ?? {},
+ rejectReason: edit.rejectReason
+ }
+ });
+ },
+ error: () => {
+ this.loadingCompareFor = null;
+ }
+ });
}
approve(edit: any): void {
@@ -189,7 +210,6 @@ export class PendingSavingsAccountEditsComponent {
}
reload(): void {
- this.closeCompare();
const url = this.router.url;
this.router
.navigateByUrl('/checker-inbox-and-tasks', { skipLocationChange: true })
diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.html
new file mode 100644
index 000000000..761528db8
--- /dev/null
+++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.html
@@ -0,0 +1,73 @@
+
+
+ difference
+
+ Proposed changes — {{ accountNo }}
+
+
+
+
+ @if (facilityRows.length === 0 && nomineeRows.length === 0) {
+
+
check_circle
+
No changes to review
+
This edit was submitted without changing any feature or nominee value.
+
+ }
+
+ @if (facilityRows.length > 0) {
+
Digital & Account Features
+
+
+
+ Feature
+ Current
+ Proposed
+
+
+
+ @for (row of facilityRows; track row.label) {
+
+ {{ row.label }}
+ {{ row.current }}
+ {{ row.proposed }}
+
+ }
+
+
+ }
+
+ @if (nomineeRows.length > 0) {
+
Nominee Change
+
+
+
+ Field
+ Proposed
+
+
+
+ @for (row of nomineeRows; track row.label) {
+
+ {{ row.label }}
+ {{ row.proposed }}
+
+ }
+
+
+ }
+
+ @if (rejectReason) {
+
Reject Reason: {{ rejectReason }}
+ }
+
+
+
+ {{ 'labels.buttons.Close' | translate }}
+
diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.scss
new file mode 100644
index 000000000..f946c19d6
--- /dev/null
+++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.scss
@@ -0,0 +1,132 @@
+/**
+ * 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/.
+ */
+
+$border: #e2e8f0;
+$muted: #64748b;
+
+.saecd-title {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ font-size: 15px;
+ font-weight: 600;
+ color: #0f172a;
+ border-bottom: 1px solid $border;
+ padding-bottom: 12px;
+ margin-bottom: 0;
+
+ &__icon {
+ color: #0ea5e9;
+ }
+}
+
+.saecd-content {
+ max-height: 70vh;
+ overflow-y: auto;
+ padding-top: 16px;
+}
+
+.saecd-section {
+ margin: 20px 0 10px;
+ font-size: 12.5px;
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.4px;
+ color: $muted;
+
+ &:first-child {
+ margin-top: 0;
+ }
+}
+
+.saecd-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 12.5px;
+
+ th,
+ td {
+ text-align: left;
+ padding: 9px 12px;
+ border-bottom: 1px solid $border;
+ vertical-align: top;
+ }
+
+ th {
+ color: $muted;
+ font-weight: 600;
+ text-transform: uppercase;
+ font-size: 11px;
+ letter-spacing: 0.4px;
+ background: #f8fafc;
+ position: sticky;
+ top: 0;
+ }
+
+ tbody tr:hover {
+ background: #fffbeb;
+ }
+
+ &__label {
+ font-weight: 600;
+ color: #1e293b;
+ width: 34%;
+ }
+
+ &__old {
+ color: #b91c1c;
+ text-decoration: line-through;
+ width: 33%;
+ }
+
+ &__new {
+ color: #15803d;
+ font-weight: 600;
+ width: 33%;
+ }
+}
+
+.saecd-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ padding: 32px 16px;
+
+ &__icon {
+ font-size: 40px;
+ width: 40px;
+ height: 40px;
+ color: #16a34a;
+ margin-bottom: 10px;
+ }
+
+ &__title {
+ margin: 0 0 6px;
+ font-size: 14px;
+ font-weight: 600;
+ color: #1e293b;
+ }
+
+ &__text {
+ margin: 0;
+ font-size: 12.5px;
+ color: $muted;
+ max-width: 420px;
+ }
+}
+
+.saecd-reject-reason {
+ margin: 20px 0 0;
+ padding: 10px 12px;
+ background: #fef2f2;
+ border: 1px solid #fecaca;
+ border-radius: 6px;
+ font-size: 12.5px;
+ color: #991b1b;
+}
diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.ts
new file mode 100644
index 000000000..0b3b1f84c
--- /dev/null
+++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/savings-account-edit-compare-dialog/savings-account-edit-compare-dialog.component.ts
@@ -0,0 +1,145 @@
+/**
+ * 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/.
+ */
+
+import { Component, OnInit, inject } from '@angular/core';
+import {
+ MAT_DIALOG_DATA,
+ MatDialogRef,
+ MatDialogTitle,
+ MatDialogContent,
+ MatDialogActions,
+ MatDialogClose
+} from '@angular/material/dialog';
+import { MatButton } from '@angular/material/button';
+import { MatIcon } from '@angular/material/icon';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+interface DiffRow {
+ label: string;
+ current: string;
+ proposed: string;
+}
+
+/** Human-readable labels for the Digital & Account Feature toggles, matching the maker's edit form. */
+const FACILITY_LABELS: Record = {
+ atmFacility: 'ATM Facility',
+ atmWithdrawalFacility: 'ATM Withdrawal',
+ upiFacility: 'UPI Facility',
+ posTransactionsFacility: 'POS Transactions',
+ chequeBook: 'Cheque Book',
+ impsFacility: 'IMPS Facility',
+ smsAlerts: 'SMS Alerts',
+ bbpsFacility: 'BBPS Facility',
+ insuranceCover: 'Insurance Cover',
+ bepgFacility: 'BEPG Facility',
+ neftFacility: 'NEFT Facility',
+ rtgsFacility: 'RTGS Facility',
+ interBankTotalCapFacility: 'Inter-Bank Total Cap',
+ withdrawalFacility: 'Withdrawal Facility',
+ depositFacilityKyc: 'Deposit Facility (KYC)',
+ virtualDebitCard: 'Virtual Debit Card'
+};
+
+const NOMINEE_LABELS: Record = {
+ operation: 'Operation',
+ nomineeId: 'Nominee Id',
+ nomineeClientId: 'Nominee Client Id',
+ relationshipId: 'Relationship Id',
+ percentageShare: 'Percentage Share',
+ isActive: 'Active',
+ nomineeIsMinor: 'Nominee Is Minor',
+ guardianClientId: 'Guardian Client Id',
+ guardianRelationshipId: 'Guardian Relationship Id',
+ effectiveDate: 'Effective Date',
+ remarks: 'Remarks'
+};
+
+/**
+ * Shows what a pending savings/current account Nominee & Digital-Feature edit actually changes, as a
+ * popup instead of an inline panel. The maker's edit form already submits only the toggles that were
+ * actually flipped (see EditSavingsAccountFeaturesComponent#buildFacilityDiff), so proposedFacilityConfig
+ * only ever carries changed fields -- this still diffs against the account's current config defensively,
+ * so an unchanged value can never appear here even if that ever stops being true upstream.
+ */
+@Component({
+ selector: 'mifosx-savings-account-edit-compare-dialog',
+ templateUrl: './savings-account-edit-compare-dialog.component.html',
+ styleUrls: ['./savings-account-edit-compare-dialog.component.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ MatDialogTitle,
+ MatDialogContent,
+ MatDialogActions,
+ MatDialogClose,
+ MatButton,
+ MatIcon
+ ]
+})
+export class SavingsAccountEditCompareDialogComponent implements OnInit {
+ dialogRef = inject>(MatDialogRef);
+ data = inject(MAT_DIALOG_DATA);
+
+ accountNo = '';
+ facilityRows: DiffRow[] = [];
+ nomineeRows: DiffRow[] = [];
+ rejectReason: string | null = null;
+
+ ngOnInit(): void {
+ this.accountNo = this.data.accountNo ?? '';
+ this.rejectReason = this.data.rejectReason ?? null;
+
+ const currentFacilityConfig = this.data.currentFacilityConfig ?? {};
+ const proposedFacilityConfig = this.data.proposedFacilityConfig ?? {};
+ this.facilityRows = Object.keys(proposedFacilityConfig)
+ .filter((key) => !this.sameValue(currentFacilityConfig[key], proposedFacilityConfig[key]))
+ .map((key) => ({
+ label: FACILITY_LABELS[key] ?? this.humanizeKey(key),
+ current: this.displayValue(currentFacilityConfig[key]),
+ proposed: this.displayValue(proposedFacilityConfig[key])
+ }))
+ .sort((a, b) => a.label.localeCompare(b.label));
+
+ const proposedNominee = this.data.proposedNominee ?? {};
+ this.nomineeRows = Object.keys(proposedNominee)
+ .filter((key) => proposedNominee[key] !== null && proposedNominee[key] !== undefined)
+ .map((key) => ({
+ label: NOMINEE_LABELS[key] ?? this.humanizeKey(key),
+ current: '—',
+ proposed: this.displayValue(proposedNominee[key])
+ }));
+ }
+
+ private sameValue(a: any, b: any): boolean {
+ if (a === undefined) {
+ a = null;
+ }
+ if (b === undefined) {
+ b = null;
+ }
+ return a === b;
+ }
+
+ private displayValue(value: any): string {
+ if (value === null || value === undefined || value === '') {
+ return '—';
+ }
+ if (typeof value === 'boolean') {
+ return value ? 'Yes' : 'No';
+ }
+ return String(value);
+ }
+
+ /** camelCase field name to a spaced, capitalised label -- fallback for anything not in the label maps. */
+ private humanizeKey(key: string): string {
+ const spaced = key
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
+ .trim();
+ return spaced.charAt(0).toUpperCase() + spaced.slice(1);
+ }
+}
diff --git a/src/app/tasks/tasks.service.ts b/src/app/tasks/tasks.service.ts
index 5f17d6809..d4acb38f2 100644
--- a/src/app/tasks/tasks.service.ts
+++ b/src/app/tasks/tasks.service.ts
@@ -199,6 +199,38 @@ export class TasksService {
return this.http.post(`/currentaccounts/${accountId}`, data, { params: httpParams });
}
+ /**
+ * Activate a savings account
+ */
+ activateSavingsAccount(accountId: any, data: any): Observable {
+ const httpParams = new HttpParams().set('command', 'activate');
+ return this.http.post(`/savingsaccounts/${accountId}`, data, { params: httpParams });
+ }
+
+ /**
+ * Activate a fixed deposit account
+ */
+ activateFDAccount(accountId: any, data: any): Observable {
+ const httpParams = new HttpParams().set('command', 'activate');
+ return this.http.post(`/fixeddepositaccounts/${accountId}`, data, { params: httpParams });
+ }
+
+ /**
+ * Activate a recurring deposit account
+ */
+ activateRDAccount(accountId: any, data: any): Observable {
+ const httpParams = new HttpParams().set('command', 'activate');
+ return this.http.post(`/recurringdepositaccounts/${accountId}`, data, { params: httpParams });
+ }
+
+ /**
+ * Activate a current account
+ */
+ activateCurrentAccount(accountId: any, data: any): Observable {
+ const httpParams = new HttpParams().set('command', 'activate');
+ return this.http.post(`/currentaccounts/${accountId}`, data, { params: httpParams });
+ }
+
/**
* Reject a savings account
*/
diff --git a/src/assets/env.js b/src/assets/env.js
index beb7fb517..8493889f7 100644
--- a/src/assets/env.js
+++ b/src/assets/env.js
@@ -18,7 +18,7 @@
window["env"]["apiActuator"] = '';
window["env"]["fineractPlatformTenantId"] = '';
- window["env"]["fineractPlatformTenantIds"] = '';
+ window["env"]["fineractPlatformTenantIds"] = 'default,socity_bank';
window['env']['tenantLogoUrl'] = '';
window['env']['tenantLogoUrlDark'] = '';
diff --git a/src/environments/.env.ts b/src/environments/.env.ts
index 3e9cc6755..ea6d58ac9 100644
--- a/src/environments/.env.ts
+++ b/src/environments/.env.ts
@@ -10,7 +10,7 @@
export default {
'mifos_x': {
'version': '260827',
- 'hash': '1273bdbe'
+ 'hash': '79ffde51'
},
'allow_switching_backend_instance': true
};