feat(tasks,savings,cashier): facility charge mapping screen, chained approve+activate, popup diff dialog, cash/transfer-only teller flow
build-run / Run Lint, Build and Deploy (push) Canceled after 0s
Publish Image in Docker Hub / build (push) Canceled after 0s
Playwright E2E Tests / Run Playwright Tests (push) Canceled after 0s
Tests / testRigor (push) Canceled after 0s

Adds a Products > Facility Charge Mappings screen so each of the 16 facility
toggles can be pointed at a real charge, backing the mapped-charge validation
added on the backend. Bulk-approve on the pending accounts screen now chains
straight into activation (matching the single-account approve flow) instead
of leaving accounts sitting in Approved; the same screen's Confirm button no
longer stays disabled on pre-filled but untouched dialogs. Add Charge is
hidden on not-yet-active savings accounts, and Transfer Funds is disabled
entirely pending a proper transfer flow. The pending savings-account edit
"Proposed Changes" panel is now a MatDialog showing only the fields that
actually changed, replacing the old always-open inline diff table. The teller
transaction-entry Payment Type dropdown is filtered to Cash/Transfer only,
mirroring the backend's retirement of the other system-defined payment types
so share withdrawal and GL transfers now settle the same way real transfers
do.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 18:46:44 +05:30
co-authored by Claude Sonnet 5
parent 0d145c72a6
commit ce840d73d7
24 changed files with 1017 additions and 203 deletions
@@ -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();
}
}
});
});