diff --git a/src/app/cashier/transaction-entry/transaction-entry.component.ts b/src/app/cashier/transaction-entry/transaction-entry.component.ts index 29522bbcc..d9ae66adb 100644 --- a/src/app/cashier/transaction-entry/transaction-entry.component.ts +++ b/src/app/cashier/transaction-entry/transaction-entry.component.ts @@ -778,23 +778,33 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { loadingAccount = false; accountError = ''; - /** Share redemption settlement is always a book-transfer to the linked savings account — never a - * cash handover. A Share (SHR) row's own TXN TP. never offers a cash payment type, and neither - * does its paired CR (savings) row in "Enter Bank Transfer" mode — that CR row's payment type is - * the one that actually governs how the settlement credit posts (see submitShareRedemptionTransfer()). - * Every other account type/mode keeps the full, unfiltered list. */ + /** + * A teller only ever picks Cash or Transfer — which specific internal settlement it actually is + * (a share redemption, a dividend payout, a GL-to-GL or GL-to-bank book entry) is business logic + * the backend already threads through from whichever payment type the clerk picked here (see + * ClerkShareWithdrawalRequestApiResource#creditLinkedSavingsAccount on the backend), not a separate + * teller-facing choice. System-defined payment types (Share Redemption Transfer, Dividend Claim + * Settlement, the loan repayment-adjustment types, ...) exist purely as internal audit labels and + * are never offered here. + * + * Share redemption settlement is always a book-transfer to the linked savings account — never a + * cash handover. A Share (SHR) row's own TXN TP. never offers a cash payment type, and neither + * does its paired CR (savings) row in "Enter Bank Transfer" mode — that CR row's payment type is + * the one that actually governs how the settlement credit posts (see submitShareRedemptionTransfer()). + */ paymentTypesForRow(row: TxnRow): any[] { + const tellerFacing = this.paymentTypes.filter((pt: any) => !pt.isSystemDefined); if (row.acTyp === 'SHR') { - return this.paymentTypes.filter((pt: any) => !pt.isCashPayment); + return tellerFacing.filter((pt: any) => !pt.isCashPayment); } const isShareSettlementCredit = row.typ === 'Enter Bank Transfer' && row.dbCr === 'Cr' && this.rows.some((r) => r.typ === 'Enter Bank Transfer' && r.dbCr === 'Dr' && r.acTyp === 'SHR'); if (isShareSettlementCredit) { - return this.paymentTypes.filter((pt: any) => !pt.isCashPayment); + return tellerFacing.filter((pt: any) => !pt.isCashPayment); } - return this.paymentTypes; + return tellerFacing; } // Per-row account id/name map: acNo → Fineract id/name (used for multi-row disbursement) @@ -1563,7 +1573,7 @@ export class TransactionEntryComponent implements OnInit, OnDestroy { if (settlementPaymentType?.isCashPayment) { this.showAlert('error', 'Cash Not Allowed for Share Transactions', [ 'Share redemption settlement is always a book-transfer to the linked savings account, never cash.', - 'Select a non-cash Payment Type on the CR row (e.g. Transfer or Share Redemption Transfer).' + 'Select Transfer as the Payment Type on the CR row.' ]); return; } diff --git a/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.html b/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.html new file mode 100644 index 000000000..4655ebf8b --- /dev/null +++ b/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.html @@ -0,0 +1,53 @@ + + +

{{ 'labels.heading.Edit Facility Charge Mapping' | translate }}

+ + +
+ + {{ 'labels.inputs.Charge' | translate }} + + @for (charge of data.chargeOptions; track charge.id) { + {{ charge.name }} ({{ charge.amount }}) + } + + @if (editMappingForm.controls.chargeId.hasError('required')) { + + {{ 'labels.inputs.Charge' | translate }} {{ 'labels.commons.is' | translate }} + {{ 'labels.commons.required' | translate }} + + } + + +
+ {{ 'labels.inputs.Amount' | translate }} + + {{ selectedChargeAmount !== null ? selectedChargeAmount : '-' }} + +
+ +
+ + {{ 'labels.inputs.Active' | translate }} + +
+
+
+ + + + + diff --git a/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.scss b/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.scss new file mode 100644 index 000000000..68c54d0dd --- /dev/null +++ b/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.scss @@ -0,0 +1,40 @@ +/** + * 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/. + */ +.efcm-form { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 340px; +} + +.efcm-field { + width: 100%; +} + +.efcm-checkbox-row { + margin: 4px 0 12px; +} + +.efcm-amount-readonly { + display: flex; + align-items: baseline; + gap: 8px; + padding: 4px 0 12px; + color: rgb(0 0 0 / 60%); + font-size: 13px; + + &__label { + font-weight: 500; + } + + &__value { + font-size: 15px; + font-weight: 600; + color: rgb(0 0 0 / 87%); + } +} diff --git a/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.ts b/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.ts new file mode 100644 index 000000000..5e00124a3 --- /dev/null +++ b/src/app/products/facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component.ts @@ -0,0 +1,71 @@ +/** + * 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/. + */ + +/** Angular Imports */ +import { Component, OnInit, inject } from '@angular/core'; +import { + MAT_DIALOG_DATA, + MatDialogRef, + MatDialogTitle, + MatDialogContent, + MatDialogActions, + MatDialogClose +} from '@angular/material/dialog'; +import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms'; +import { CdkScrollable } from '@angular/cdk/scrolling'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +/** + * Lets an admin change which charge a single facility (e.g. ATM_FACILITY) maps to. The amount charged is + * always the selected charge's own, live amount (see FacilityChargeAutoApplyService, which reads + * charge.getAmount() when applying it) -- shown here read-only for reference, not independently editable. + * To change the amount, edit the Charge itself under Products > Charges. + */ +@Component({ + selector: 'mifosx-edit-facility-charge-mapping-dialog', + templateUrl: './edit-facility-charge-mapping-dialog.component.html', + styleUrls: ['./edit-facility-charge-mapping-dialog.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatDialogTitle, + CdkScrollable, + MatDialogContent, + MatDialogActions, + MatDialogClose + ] +}) +export class EditFacilityChargeMappingDialogComponent implements OnInit { + dialogRef = inject>(MatDialogRef); + private formBuilder = inject(UntypedFormBuilder); + data = inject(MAT_DIALOG_DATA); + + editMappingForm: UntypedFormGroup; + + ngOnInit(): void { + this.editMappingForm = this.formBuilder.group({ + chargeId: [ + this.data.mapping.chargeId, + Validators.required + ], + // A facility with no charge mapped yet is being configured for the first time here -- default + // Active to checked so picking a charge and submitting is enough to make it usable immediately. + isActive: [this.data.mapping.chargeId ? this.data.mapping.isActive : true] + }); + } + + /** Amount of whichever charge is currently selected in the form -- updates live as the selection changes. */ + get selectedChargeAmount(): number | null { + const selectedId = this.editMappingForm?.get('chargeId')?.value; + const selected = (this.data.chargeOptions || []).find((charge: any) => charge.id === selectedId); + return selected ? selected.amount : null; + } + + submit(): void { + this.dialogRef.close(this.editMappingForm.value); + } +} diff --git a/src/app/products/facility-charge-mappings/facility-charge-mappings.component.html b/src/app/products/facility-charge-mappings/facility-charge-mappings.component.html new file mode 100644 index 000000000..5dab50d9c --- /dev/null +++ b/src/app/products/facility-charge-mappings/facility-charge-mappings.component.html @@ -0,0 +1,76 @@ + + +
+

{{ 'labels.heading.Facility Charge Mappings' | translate }}

+

+ {{ + 'labels.text.Which charge is applied when a facility (ATM, UPI, Cheque Book, ...) is enabled on an account' + | translate + }} +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ 'labels.inputs.Facility' | translate }}{{ facilityLabel(mapping.facilityType) }}{{ 'labels.inputs.Mapped Charge' | translate }} + {{ mapping.chargeName || '—' }} + @if (mapping.chargeId && !mapping.chargeAvailable) { + + + {{ 'labels.text.Deleted' | translate }} + + } + {{ 'labels.inputs.Amount' | translate }}{{ mapping.defaultAmount !== null ? mapping.defaultAmount : '—' }}{{ 'labels.inputs.Active' | translate }} + {{ (mapping.isActive ? 'labels.commons.Yes' : 'labels.commons.No') | translate }} + {{ 'labels.inputs.Actions' | translate }} + +
+ + +
diff --git a/src/app/products/facility-charge-mappings/facility-charge-mappings.component.scss b/src/app/products/facility-charge-mappings/facility-charge-mappings.component.scss new file mode 100644 index 000000000..72564daa7 --- /dev/null +++ b/src/app/products/facility-charge-mappings/facility-charge-mappings.component.scss @@ -0,0 +1,35 @@ +/** + * 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/. + */ +.fcm-header { + margin-bottom: 20px; + + h1 { + margin: 0 0 4px; + font-size: 20px; + font-weight: 600; + } + + &__sub { + margin: 0; + color: rgb(0 0 0 / 60%); + font-size: 13px; + } +} + +.fcm-broken-badge { + display: inline-flex; + align-items: center; + gap: 4px; + margin-left: 8px; + padding: 2px 8px; + border-radius: 10px; + background: rgb(211 47 47 / 10%); + color: #d32f2f; + font-size: 11px; + font-weight: 600; +} diff --git a/src/app/products/facility-charge-mappings/facility-charge-mappings.component.ts b/src/app/products/facility-charge-mappings/facility-charge-mappings.component.ts new file mode 100644 index 000000000..185350bc1 --- /dev/null +++ b/src/app/products/facility-charge-mappings/facility-charge-mappings.component.ts @@ -0,0 +1,144 @@ +/** + * 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/. + */ + +/** Angular Imports */ +import { Component, OnInit, ViewChild, inject } from '@angular/core'; +import { MatPaginator } from '@angular/material/paginator'; +import { MatSort, MatSortHeader } from '@angular/material/sort'; +import { + MatTableDataSource, + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow +} from '@angular/material/table'; +import { ActivatedRoute } from '@angular/router'; +import { MatDialog } from '@angular/material/dialog'; +import { MatTooltip } from '@angular/material/tooltip'; + +/** rxjs Imports */ +import { EMPTY, Observable } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; + +/** Custom Services */ +import { ProductsService } from '../products.service'; +import { AlertService } from 'app/core/alert/alert.service'; +import { EditFacilityChargeMappingDialogComponent } from './edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +/** + * Lists which charge is applied when each of the 16 named account facilities (ATM Facility, UPI + * Facility, Cheque Book, ...) is enabled on a savings/current account, and lets an admin change it. + */ +@Component({ + selector: 'mifosx-facility-charge-mappings', + templateUrl: './facility-charge-mappings.component.html', + styleUrls: ['./facility-charge-mappings.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + MatTable, + MatSort, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatSortHeader, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow, + MatPaginator, + MatTooltip + ] +}) +export class FacilityChargeMappingsComponent implements OnInit { + private route = inject(ActivatedRoute); + private productsService = inject(ProductsService); + private dialog = inject(MatDialog); + private alertService = inject(AlertService); + + facilityChargeMappingsData: any[] = []; + displayedColumns: string[] = [ + 'facilityType', + 'chargeName', + 'defaultAmount', + 'isActive', + 'actions' + ]; + dataSource: MatTableDataSource; + + @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor() { + this.route.data.subscribe((data: { facilityChargeMappings: any }) => { + this.facilityChargeMappingsData = data.facilityChargeMappings; + }); + } + + ngOnInit() { + this.setFacilityChargeMappings(); + } + + setFacilityChargeMappings() { + this.dataSource = new MatTableDataSource(this.facilityChargeMappingsData); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + } + + /** Human-readable label for a facility_type code, e.g. ATM_FACILITY -> ATM Facility. */ + facilityLabel(facilityType: string): string { + if (!facilityType) { + return ''; + } + return facilityType + .toLowerCase() + .split('_') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + } + + editMapping(mapping: any): void { + this.productsService + .getFacilityChargeMappingsTemplate() + .pipe( + switchMap((template: any) => + this.dialog + .open(EditFacilityChargeMappingDialogComponent, { + data: { mapping, chargeOptions: template.chargeOptions || [] } + }) + .afterClosed() + ), + switchMap((formValue: any): Observable => { + if (!formValue) { + return EMPTY; + } + return this.productsService.updateFacilityChargeMapping(mapping.id, formValue); + }) + ) + .subscribe((result: any) => { + if (result) { + this.alertService.alert({ + type: 'Facility Charge Mapping', + message: `${this.facilityLabel(mapping.facilityType)} mapping updated successfully.` + }); + this.productsService.getFacilityChargeMappings().subscribe((mappings: any) => { + this.facilityChargeMappingsData = mappings; + this.setFacilityChargeMappings(); + }); + } + }); + } +} diff --git a/src/app/products/facility-charge-mappings/facility-charge-mappings.resolver.ts b/src/app/products/facility-charge-mappings/facility-charge-mappings.resolver.ts new file mode 100644 index 000000000..cafbd6226 --- /dev/null +++ b/src/app/products/facility-charge-mappings/facility-charge-mappings.resolver.ts @@ -0,0 +1,32 @@ +/** + * 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/. + */ + +/** Angular Imports */ +import { Injectable, inject } from '@angular/core'; + +/** rxjs Imports */ +import { Observable } from 'rxjs'; + +/** Custom Services */ +import { ProductsService } from '../products.service'; + +/** + * Facility charge mappings data resolver. + */ +@Injectable() +export class FacilityChargeMappingsResolver { + private productsService = inject(ProductsService); + + /** + * Returns all 16 facility -> charge mappings. + * @returns {Observable} + */ + resolve(): Observable { + return this.productsService.getFacilityChargeMappings(); + } +} diff --git a/src/app/products/products-routing.module.ts b/src/app/products/products-routing.module.ts index d724358fa..cacec80f1 100644 --- a/src/app/products/products-routing.module.ts +++ b/src/app/products/products-routing.module.ts @@ -33,6 +33,7 @@ import { ViewRecurringDepositProductComponent } from './recurring-deposit-produc import { ChargesComponent } from './charges/charges.component'; import { ViewChargeComponent } from './charges/view-charge/view-charge.component'; import { CreateChargeComponent } from './charges/create-charge/create-charge.component'; +import { FacilityChargeMappingsComponent } from './facility-charge-mappings/facility-charge-mappings.component'; import { FixedDepositProductsComponent } from './fixed-deposit-products/fixed-deposit-products.component'; import { CreateFixedDepositProductComponent } from './fixed-deposit-products/create-fixed-deposit-product/create-fixed-deposit-product.component'; import { ProductsMixComponent } from './products-mix/products-mix.component'; @@ -76,6 +77,7 @@ import { ShareProductAndTemplateResolver } from './share-products/edit-share-pro import { RecurringDepositProductsResolver } from './recurring-deposit-products/recurring-deposit-products.resolver'; import { RecurringDepositProductResolver } from './recurring-deposit-products/recurring-deposit-product.resolver'; import { ChargesResolver } from './charges/charges.resolver'; +import { FacilityChargeMappingsResolver } from './facility-charge-mappings/facility-charge-mappings.resolver'; import { ChargeResolver } from './charges/charge.resolver'; import { ChargesTemplateResolver } from './charges/charges-template.resolver'; import { FixedDepositProductsResolver } from './fixed-deposit-products/fixed-deposit-products.resolver'; @@ -937,6 +939,14 @@ const routes: Routes = [ } ] }, + { + path: 'facility-charge-mappings', + data: { title: 'Facility Charge Mappings', breadcrumb: 'Facility Charge Mappings' }, + component: FacilityChargeMappingsComponent, + resolve: { + facilityChargeMappings: FacilityChargeMappingsResolver + } + }, { path: 'collaterals', data: { title: 'Collaterals', breadcrumb: 'Collaterals' }, @@ -1015,6 +1025,7 @@ const routes: Routes = [ ChargeResolver, ChargesTemplateAndResolver, ChargesTemplateResolver, + FacilityChargeMappingsResolver, FixedDepositProductsResolver, FixedDepositProductsTemplateResolver, ProductsMixResolver, diff --git a/src/app/products/products.component.html b/src/app/products/products.component.html index 3345cdb55..f07c81fe7 100644 --- a/src/app/products/products.component.html +++ b/src/app/products/products.component.html @@ -151,6 +151,22 @@ + +
+ +
+
+

{{ 'labels.heading.Facility Charge Mappings' | translate }}

+

+ {{ + 'labels.text.Choose which charge applies when an account facility (ATM, UPI, Cheque Book, ...) is enabled' + | translate + }} +

+
+ +
+
diff --git a/src/app/products/products.module.ts b/src/app/products/products.module.ts index 27675d51e..5828bd3c7 100644 --- a/src/app/products/products.module.ts +++ b/src/app/products/products.module.ts @@ -30,6 +30,8 @@ import { CreateLoanProductComponent } from './loan-products/create-loan-product/ import { ManageTaxConfigurationsComponent } from './manage-tax-configurations/manage-tax-configurations.component'; import { RecurringDepositProductsComponent } from './recurring-deposit-products/recurring-deposit-products.component'; import { ChargesComponent } from './charges/charges.component'; +import { FacilityChargeMappingsComponent } from './facility-charge-mappings/facility-charge-mappings.component'; +import { EditFacilityChargeMappingDialogComponent } from './facility-charge-mappings/edit-facility-charge-mapping-dialog/edit-facility-charge-mapping-dialog.component'; import { ViewChargeComponent } from './charges/view-charge/view-charge.component'; import { FixedDepositProductsComponent } from './fixed-deposit-products/fixed-deposit-products.component'; import { ProductsMixComponent } from './products-mix/products-mix.component'; @@ -159,6 +161,8 @@ import { AdvancedAccountingMappingRuleComponent } from './loan-products/loan-pro RecurringDepositProductsComponent, ChargesComponent, ViewChargeComponent, + FacilityChargeMappingsComponent, + EditFacilityChargeMappingDialogComponent, FixedDepositProductsComponent, ManageTaxComponentsComponent, ProductsMixComponent, diff --git a/src/app/products/products.service.ts b/src/app/products/products.service.ts index f29376e79..c15139d14 100644 --- a/src/app/products/products.service.ts +++ b/src/app/products/products.service.ts @@ -347,6 +347,29 @@ export class ProductsService { return this.http.get('/charges/template'); } + /** + * @returns {Observable} All 16 facility -> charge mappings. + */ + getFacilityChargeMappings(): Observable { + return this.http.get('/facilitychargemappings'); + } + + /** + * @returns {Observable} Charge options available to map a facility to. + */ + getFacilityChargeMappingsTemplate(): Observable { + return this.http.get('/facilitychargemappings/template'); + } + + /** + * @param {string} mappingId Facility charge mapping ID to update. + * @param {any} mapping New chargeId (and optionally defaultAmount/isActive). + * @returns {Observable} + */ + updateFacilityChargeMapping(mappingId: string, mapping: any): Observable { + return this.http.put(`/facilitychargemappings/${mappingId}`, mapping); + } + /** * @param {string} chargeId Charge ID of charge. * @returns {Observable} Charge. diff --git a/src/app/savings/savings-account-view/savings-account-view.component.ts b/src/app/savings/savings-account-view/savings-account-view.component.ts index 2d0b9f17b..b9e92ffdf 100644 --- a/src/app/savings/savings-account-view/savings-account-view.component.ts +++ b/src/app/savings/savings-account-view/savings-account-view.component.ts @@ -28,6 +28,7 @@ import { AccountsService } from 'app/accounts/accounts.service'; import { SettingsService } from 'app/settings/settings.service'; import { Dates } from 'app/core/utils/dates'; import { AuthenticationService } from '../../core/authentication/authentication.service'; +import { AlertService } from 'app/core/alert/alert.service'; /** Environment Configuration */ import { environment } from '../../../environments/environment'; @@ -85,6 +86,7 @@ export class SavingsAccountViewComponent implements OnInit { private settingsService = inject(SettingsService); private dateUtils = inject(Dates); private authenticationService = inject(AuthenticationService); + private alertService = inject(AlertService); dialog = inject(MatDialog); /** Savings Account Data */ @@ -225,12 +227,8 @@ export class SavingsAccountViewComponent implements OnInit { this.isActive = status === 'Active'; const subStatus = this.savingsAccountData.subStatus; this.buttonConfig = new SavingsButtonsConfiguration(status, subStatus); - if (this.savingsAccountData.clientId) { - this.buttonConfig.addOption({ - name: 'Transfer Funds', - taskPermissionName: 'CREATE_ACCOUNTTRANSFER' - }); - } + // Transfer Funds is temporarily disabled for savings accounts (pending and active alike) -- + // deliberately not added to the menu here; see the guarded 'Transfer Funds' switch case below. if (this.savingsAccountData.externalId && environment.mifosInterbankTransfersEnabled) { this.buttonConfig.addOption({ name: 'Interbank Transfer', @@ -347,15 +345,11 @@ export class SavingsAccountViewComponent implements OnInit { this.disableWithHoldTax(); break; case 'Transfer Funds': - const queryParams: any = { - interbank: false, - savingsId: this.savingsAccountData.id, - accountType: 'fromsavings' - }; - this.router.navigate(['transfer-funds/make-account-transfer'], { - relativeTo: this.route, - queryParams: queryParams, - state: { balance: this.savingsAccountData.summary.availableBalance } + // Temporarily disabled for savings accounts -- blocked here too, not just left out of the + // menu, so a stale menu render or direct invocation can't reach the transfer screen. + this.alertService.alert({ + type: 'Transfer Funds', + message: 'Transfer Funds is temporarily unavailable for savings accounts.' }); break; case 'Interbank Transfer': { diff --git a/src/app/savings/savings-account-view/savings-buttons.config.ts b/src/app/savings/savings-account-view/savings-buttons.config.ts index 930167fb5..2caaf3f0e 100644 --- a/src/app/savings/savings-account-view/savings-buttons.config.ts +++ b/src/app/savings/savings-account-view/savings-buttons.config.ts @@ -189,10 +189,6 @@ export class SavingsButtonsConfiguration { name: 'Withdrawn by Client', taskPermissionName: 'WITHDRAW_SAVINGSACCOUNT' }, - { - name: 'Add Charge', - taskPermissionName: 'CREATE_SAVINGSACCOUNTCHARGE' - }, { name: 'Delete', taskPermissionName: 'DELETE_SAVINGSACCOUNT' diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.ts index 8fa43a3fe..aea0233d3 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.ts +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.ts @@ -9,6 +9,7 @@ import { Component, inject } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { SelectionModel } from '@angular/cdk/collections'; +import { switchMap } from 'rxjs/operators'; import { MatTableDataSource, MatTable, @@ -141,7 +142,8 @@ export class PendingAccountComponent { data: { title: 'Enter Approval Date', layout: { addButtonText: 'Confirm' }, - formfields + formfields, + pristine: false } }); dialogRef.afterClosed().subscribe((response: any) => { @@ -166,7 +168,8 @@ export class PendingAccountComponent { data: { title: 'Enter Rejection Date', layout: { addButtonText: 'Confirm' }, - formfields + formfields, + pristine: false } }); dialogRef.afterClosed().subscribe((response: any) => { @@ -183,7 +186,7 @@ export class PendingAccountComponent { const payload = { rejectedOnDate, dateFormat, locale }; const selected = this.selection.selected; - let done = 0; + let settled = 0; selected.forEach((account: any) => { const depositTypeId = account.depositType?.id; let reject$; @@ -196,39 +199,72 @@ export class PendingAccountComponent { } else { reject$ = this.tasksService.rejectSavingsAccount(account.id, payload); } - reject$.subscribe(() => { - done++; - if (done === selected.length) { - this.reload(); + reject$.subscribe({ + next: () => { + settled++; + if (settled === selected.length) { + this.reload(); + } + }, + error: () => { + settled++; + if (settled === selected.length) { + this.reload(); + } } }); }); } + /** + * Approves an 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 checker only sees and clicks through the one bulk action, matching what happens when + * approving from inside an individual account (see e.g. ApproveSavingsAccountComponent). + */ + private approveAndActivate$(account: any, approvedOnDate: string, dateFormat: string, locale: string) { + const approvePayload = { approvedOnDate, dateFormat, locale }; + const activatePayload = { activatedOnDate: approvedOnDate, dateFormat, locale }; + const depositTypeId = account.depositType?.id; + + let approve$; + let activate$; + if (account.accountType === 'Current') { + approve$ = this.tasksService.approveCurrentAccount(account.id, approvePayload); + activate$ = () => this.tasksService.activateCurrentAccount(account.id, activatePayload); + } else if (depositTypeId === 200) { + approve$ = this.tasksService.approveFDAccount(account.id, approvePayload); + activate$ = () => this.tasksService.activateFDAccount(account.id, activatePayload); + } else if (depositTypeId === 300) { + approve$ = this.tasksService.approveRDAccount(account.id, approvePayload); + activate$ = () => this.tasksService.activateRDAccount(account.id, activatePayload); + } else { + approve$ = this.tasksService.approveSavingsAccount(account.id, approvePayload); + activate$ = () => this.tasksService.activateSavingsAccount(account.id, activatePayload); + } + return approve$.pipe(switchMap(activate$)); + } + private bulkApprove(submittedData: any) { const dateFormat = this.settingsService.dateFormat; const locale = this.settingsService.language.code; const approvedOnDate = this.dateUtils.formatDate(submittedData.value.approvedOnDate, dateFormat); - const payload = { approvedOnDate, dateFormat, locale }; const selected = this.selection.selected; - let done = 0; + let settled = 0; selected.forEach((account: any) => { - const depositTypeId = account.depositType?.id; - let approve$; - if (account.accountType === 'Current') { - approve$ = this.tasksService.approveCurrentAccount(account.id, payload); - } else if (depositTypeId === 200) { - approve$ = this.tasksService.approveFDAccount(account.id, payload); - } else if (depositTypeId === 300) { - approve$ = this.tasksService.approveRDAccount(account.id, payload); - } else { - approve$ = this.tasksService.approveSavingsAccount(account.id, payload); - } - approve$.subscribe(() => { - done++; - if (done === selected.length) { - this.reload(); + this.approveAndActivate$(account, approvedOnDate, dateFormat, locale).subscribe({ + next: () => { + settled++; + if (settled === selected.length) { + this.reload(); + } + }, + error: () => { + settled++; + if (settled === selected.length) { + this.reload(); + } } }); }); diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.html index d9aaafb86..e11ff7345 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.html +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-account-edits/pending-savings-account-edits.component.html @@ -68,8 +68,13 @@ Actions -
- - @if (selectedEdit) { -
-
-

Proposed Changes — Account "{{ selectedEdit.accountNo }}"

- -
- - @if (selectedEdit.facilityConfigSubmitted) { -
-

Digital & Account Features

- - - - - - - - - @for (row of entries(selectedEdit.proposedFacilityConfig); track row.key) { - - - - - } - -
FeatureProposed Value
{{ row.key }}{{ row.value }}
-
- } - - @if (selectedEdit.nomineeSubmitted) { -
-

Nominee Change

- - - - - - - - - @for (row of entries(selectedEdit.proposedNominee); track row.key) { - - - - - } - -
FieldProposed Value
{{ 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

+ + + + + + + + + + @for (row of facilityRows; track row.label) { + + + + + + } + +
FeatureCurrentProposed
{{ row.label }}{{ row.current }}{{ row.proposed }}
+ } + + @if (nomineeRows.length > 0) { +

Nominee Change

+ + + + + + + + + @for (row of nomineeRows; track row.label) { + + + + + } + +
FieldProposed
{{ row.label }}{{ row.proposed }}
+ } + + @if (rejectReason) { +

Reject Reason: {{ rejectReason }}

+ } +
+ + + + 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 };