From 7b9a6394c8767ff13d63d31687c944c46e66f827 Mon Sep 17 00:00:00 2001 From: anshul Date: Thu, 24 Sep 2026 17:22:50 +0530 Subject: [PATCH] Implement organization maker-checker workflows --- package.json | 2 +- src/app/login/login.component.ts | 2 +- .../branch-profile.component.ts | 20 ++- .../institution-profile.component.ts | 13 +- .../create-payment-type.component.ts | 10 +- .../edit-payment-type.component.ts | 10 +- .../payment-types/payment-types.component.ts | 19 ++- .../working-days/working-days.component.ts | 10 +- .../apply-shares/apply-shares.component.html | 6 +- .../apply-collection-dialog.component.html | 6 +- .../gl-settings-dialog.component.ts | 10 +- .../sponsor-bank-list.component.ts | 24 +++- ...nfigure-maker-checker-tasks.component.html | 30 ++++- ...nfigure-maker-checker-tasks.component.scss | 29 +++++ ...configure-maker-checker-tasks.component.ts | 57 +++++++- .../pending-account.component.html | 101 +++++++++++++- .../pending-account.component.scss | 10 ++ .../pending-account.component.ts | 123 +++++++++++++++++- ...nding-savings-account-edits.component.html | 1 + ...nding-savings-account-edits.component.scss | 5 + ...pending-savings-account-edits.component.ts | 112 +++++++++++++--- ...account-edit-compare-dialog.component.html | 4 +- ...s-account-edit-compare-dialog.component.ts | 114 ++++++++++++++-- .../pending-user-edits.component.ts | 1 - .../user-edit-compare-dialog.component.html | 4 +- .../user-edit-compare-dialog.component.ts | 4 + .../checker-inbox-and-tasks.component.html | 20 --- src/app/tasks/maker-checker-entity-fetch.ts | 27 +++- src/app/tasks/tasks-routing.module.ts | 10 +- .../view-checker-inbox.component.ts | 93 ++++++++++++- 30 files changed, 787 insertions(+), 90 deletions(-) diff --git a/package.json b/package.json index 4e0057794..aad5a8105 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "ng": "ng", "build": "node version.js && npm run env -s && ng build --configuration production --output-hashing=none", "build:prod": "node version.js && node --max-old-space-size=16384 ./node_modules/@angular/cli/bin/ng build --configuration production --output-hashing=none --base-href=/web-app/", - "start": "npm run env -s && cross-env NODE_OPTIONS=\"--max_old_space_size=16384\" ng serve --proxy-config proxy.localhost.conf.js", + "start": "npm run env -s && cross-env NODE_OPTIONS=\"--max_old_space_size=4096\" ng serve --proxy-config proxy.localhost.conf.js", "start:share": "npm run env -s && cross-env NODE_OPTIONS=\"--max_old_space_size=16384\" ng serve --host 0.0.0.0 --proxy-config proxy.localhost.conf.js", "start:demo": "npm run env -s && cross-env NODE_OPTIONS=\"--max_old_space_size=16384\" ng serve --proxy-config proxy.conf.js", "serve:dev": "npm run env -s && cross-env NODE_OPTIONS=\"--max_old_space_size=16384\" ng serve --source-map --proxy-config proxy.localhost.conf.js", diff --git a/src/app/login/login.component.ts b/src/app/login/login.component.ts index 4bf3298d2..e69f7b89b 100644 --- a/src/app/login/login.component.ts +++ b/src/app/login/login.component.ts @@ -438,7 +438,7 @@ export class LoginComponent implements OnInit, OnDestroy { .split(',') .map((id) => id.trim()) .filter((id) => id.length > 0); - if (tenantIds.length === 0 || (tenantIds.length === 1 && tenantIds[0] === 'default')) { + if (tenantIds.length === 0) { return false; } return true; diff --git a/src/app/organization/branch-profile/branch-profile.component.ts b/src/app/organization/branch-profile/branch-profile.component.ts index 628d0cb6b..564d8edd1 100644 --- a/src/app/organization/branch-profile/branch-profile.component.ts +++ b/src/app/organization/branch-profile/branch-profile.component.ts @@ -143,11 +143,25 @@ export class BranchProfileComponent implements OnInit { .subscribe((res: any) => { this.saving = false; if (!res) return; + if (res.rollbackTransaction) { + // Parked for checker approval - the branch profile has not actually changed yet. + this.successMsg = 'Branch profile update has been submitted for approval.'; + this.form.markAsPristine(); + return; + } this.successMsg = 'Branch profile saved.'; this.form.markAsPristine(); - this.lastUpdated = res.updatedByUsername ? { by: res.updatedByUsername, at: res.updatedAt } : null; - const idx = this.offices.findIndex((o) => o.officeId === this.selectedOfficeId); - if (idx >= 0) this.offices[idx] = res; + // The response is now a CommandProcessingResult, not the updated row (the write goes through + // the command pipeline) - reload the saved row from the server instead of patching state from + // this response. + this.organizationService.getBranchProfile(this.selectedOfficeId!).subscribe((updated: any) => { + if (!updated) return; + this.lastUpdated = updated.updatedByUsername + ? { by: updated.updatedByUsername, at: updated.updatedAt } + : null; + const idx = this.offices.findIndex((o) => o.officeId === this.selectedOfficeId); + if (idx >= 0) this.offices[idx] = updated; + }); }); } } diff --git a/src/app/organization/institution-profile/institution-profile.component.ts b/src/app/organization/institution-profile/institution-profile.component.ts index 2d04f9480..db05e8a75 100644 --- a/src/app/organization/institution-profile/institution-profile.component.ts +++ b/src/app/organization/institution-profile/institution-profile.component.ts @@ -182,9 +182,18 @@ export class InstitutionProfileComponent implements OnInit { .subscribe((res: any) => { this.saving = false; if (!res) return; + if (res.rollbackTransaction) { + // Parked for checker approval - the profile has not actually changed yet, so the form must + // not be patched with the submitted (not-yet-applied) values as if it had. + this.successMsg = 'Bank profile update has been submitted for approval.'; + this.form.markAsPristine(); + return; + } this.successMsg = 'Bank profile saved.'; - this.applyResult(res); - this.form.markAsPristine(); + // The response is now a CommandProcessingResult, not the updated row (the write goes through + // the command pipeline) - reload the saved row from the server instead of patching the form + // from this response. + this.load(); }); } diff --git a/src/app/organization/payment-types/create-payment-type/create-payment-type.component.ts b/src/app/organization/payment-types/create-payment-type/create-payment-type.component.ts index d16092a72..c3b0d26b6 100644 --- a/src/app/organization/payment-types/create-payment-type/create-payment-type.component.ts +++ b/src/app/organization/payment-types/create-payment-type/create-payment-type.component.ts @@ -16,6 +16,7 @@ import { OrganizationService } from '../../organization.service'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; import { MatCheckbox } from '@angular/material/checkbox'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { AlertService } from 'app/core/alert/alert.service'; /** * Create Payment Type Component. @@ -35,6 +36,7 @@ export class CreatePaymentTypeComponent implements OnInit { private organizationService = inject(OrganizationService); private router = inject(Router); private route = inject(ActivatedRoute); + private alertService = inject(AlertService); /** Payment Type form. */ paymentTypeForm: UntypedFormGroup; @@ -73,7 +75,13 @@ export class CreatePaymentTypeComponent implements OnInit { */ submit() { const paymentType = this.paymentTypeForm.value; - this.organizationService.createPaymentType(paymentType).subscribe((response) => { + this.organizationService.createPaymentType(paymentType).subscribe((response: any) => { + if (response?.rollbackTransaction) { + this.alertService.alert({ + type: 'Success', + message: 'Payment Type creation has been submitted for approval.' + }); + } this.router.navigate(['../'], { relativeTo: this.route }); }); } diff --git a/src/app/organization/payment-types/edit-payment-type/edit-payment-type.component.ts b/src/app/organization/payment-types/edit-payment-type/edit-payment-type.component.ts index 7ec31a1c2..81eda5756 100644 --- a/src/app/organization/payment-types/edit-payment-type/edit-payment-type.component.ts +++ b/src/app/organization/payment-types/edit-payment-type/edit-payment-type.component.ts @@ -16,6 +16,7 @@ import { OrganizationService } from 'app/organization/organization.service'; import { CdkTextareaAutosize } from '@angular/cdk/text-field'; import { MatCheckbox } from '@angular/material/checkbox'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { AlertService } from 'app/core/alert/alert.service'; /** * Edit Payment Type component. @@ -35,6 +36,7 @@ export class EditPaymentTypeComponent implements OnInit { private organizationService = inject(OrganizationService); private router = inject(Router); private route = inject(ActivatedRoute); + private alertService = inject(AlertService); /** Payment Type form. */ paymentTypeForm: UntypedFormGroup; @@ -88,7 +90,13 @@ export class EditPaymentTypeComponent implements OnInit { */ submit() { const paymentType = this.paymentTypeForm.value; - this.organizationService.updatePaymentType(this.paymentTypeData.id, paymentType).subscribe((response) => { + this.organizationService.updatePaymentType(this.paymentTypeData.id, paymentType).subscribe((response: any) => { + if (response?.rollbackTransaction) { + this.alertService.alert({ + type: 'Success', + message: 'Payment Type update has been submitted for approval.' + }); + } this.router.navigate(['../../'], { relativeTo: this.route }); }); } diff --git a/src/app/organization/payment-types/payment-types.component.ts b/src/app/organization/payment-types/payment-types.component.ts index d883877e1..189f7efa5 100644 --- a/src/app/organization/payment-types/payment-types.component.ts +++ b/src/app/organization/payment-types/payment-types.component.ts @@ -34,6 +34,7 @@ import { DeleteDialogComponent } from '../../shared/delete-dialog/delete-dialog. import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatTooltip } from '@angular/material/tooltip'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { AlertService } from 'app/core/alert/alert.service'; /** * Payment Types component. @@ -65,6 +66,7 @@ export class PaymentTypesComponent implements OnInit { private organizationService = inject(OrganizationService); private route = inject(ActivatedRoute); private dialog = inject(MatDialog); + private alertService = inject(AlertService); /** Payment Types data. */ paymentTypesData: any; @@ -132,9 +134,20 @@ export class PaymentTypesComponent implements OnInit { }); deletePaymentTypeDialogRef.afterClosed().subscribe((response: any) => { if (response.delete) { - this.organizationService.deletePaymentType(paymentTypeId).subscribe(() => { - this.paymentTypesData = this.paymentTypesData.filter((paymentType: any) => paymentType.id !== paymentTypeId); - this.setPaymentTypes(); + this.organizationService.deletePaymentType(paymentTypeId).subscribe((deleteResponse: any) => { + if (deleteResponse?.rollbackTransaction) { + // Parked for checker approval - the record has not actually been removed yet, so the + // list must not drop it (that would falsely show the deletion as already applied). + this.alertService.alert({ + type: 'Success', + message: 'Payment Type deletion has been submitted for approval.' + }); + } else { + this.paymentTypesData = this.paymentTypesData.filter( + (paymentType: any) => paymentType.id !== paymentTypeId + ); + this.setPaymentTypes(); + } }); } }); diff --git a/src/app/organization/working-days/working-days.component.ts b/src/app/organization/working-days/working-days.component.ts index b175dafc1..dc88ae7ab 100644 --- a/src/app/organization/working-days/working-days.component.ts +++ b/src/app/organization/working-days/working-days.component.ts @@ -23,6 +23,7 @@ import { OrganizationService } from '../organization.service'; import { SettingsService } from 'app/settings/settings.service'; import { PopoverService } from '../../configuration-wizard/popover/popover.service'; import { ConfigurationWizardService } from '../../configuration-wizard/configuration-wizard.service'; +import { AlertService } from 'app/core/alert/alert.service'; /** Custom Dialog Components */ import { NextStepDialogComponent } from '../../configuration-wizard/next-step-dialog/next-step-dialog.component'; @@ -53,6 +54,7 @@ export class WorkingDaysComponent implements OnInit, AfterViewInit { private dialog = inject(MatDialog); private configurationWizardService = inject(ConfigurationWizardService); private popoverService = inject(PopoverService); + private alertService = inject(AlertService); /** Working days form. */ workingDaysForm: UntypedFormGroup; @@ -152,7 +154,13 @@ export class WorkingDaysComponent implements OnInit, AfterViewInit { } } workingDays.recurrence = recurrence; - this.organizationService.updateWorkingDays(workingDays).subscribe((response) => { + this.organizationService.updateWorkingDays(workingDays).subscribe((response: any) => { + if (response?.rollbackTransaction) { + this.alertService.alert({ + type: 'Success', + message: 'Working Days update has been submitted for approval.' + }); + } if (this.configurationWizardService.showDefineWorkingDays) { this.configurationWizardService.showDefineWorkingDays = false; this.openNextStepDialog(); diff --git a/src/app/shares/shares-account-actions/apply-shares/apply-shares.component.html b/src/app/shares/shares-account-actions/apply-shares/apply-shares.component.html index 0299ae92f..ee9cf450c 100644 --- a/src/app/shares/shares-account-actions/apply-shares/apply-shares.component.html +++ b/src/app/shares/shares-account-actions/apply-shares/apply-shares.component.html @@ -226,8 +226,10 @@ Submitting… } @else { - check - Submit Request + + check + Submit Request + } diff --git a/src/app/shares/shares-account-view/apply-collection-dialog/apply-collection-dialog.component.html b/src/app/shares/shares-account-view/apply-collection-dialog/apply-collection-dialog.component.html index 4a91e4673..9657f6992 100644 --- a/src/app/shares/shares-account-view/apply-collection-dialog/apply-collection-dialog.component.html +++ b/src/app/shares/shares-account-view/apply-collection-dialog/apply-collection-dialog.component.html @@ -275,8 +275,10 @@ Applying… } @else { - check - Apply Collection + + check + Apply Collection + } diff --git a/src/app/sponsor-dd/gl-settings-dialog/gl-settings-dialog.component.ts b/src/app/sponsor-dd/gl-settings-dialog/gl-settings-dialog.component.ts index d2f1315f5..16fdcda36 100644 --- a/src/app/sponsor-dd/gl-settings-dialog/gl-settings-dialog.component.ts +++ b/src/app/sponsor-dd/gl-settings-dialog/gl-settings-dialog.component.ts @@ -297,8 +297,14 @@ export class GlSettingsDialogComponent implements OnInit { ddLimitAmount: val.ddLimitAmount }) .subscribe({ - next: () => { - this.snackBar.open('Sponsor DD GL Mapping & GST Settings saved successfully!', 'Close', { duration: 3000 }); + next: (res: any) => { + // A save submitted for approval comes back HTTP 200 with rollbackTransaction: true - the + // mapping has not actually been applied yet, so this must not claim success the same way an + // immediate save does. + const message = res?.rollbackTransaction + ? 'Sponsor DD GL Mapping & GST Settings submitted for approval' + : 'Sponsor DD GL Mapping & GST Settings saved successfully!'; + this.snackBar.open(message, 'Close', { duration: res?.rollbackTransaction ? 4000 : 3000 }); this.dialogRef.close(true); }, error: (err) => { diff --git a/src/app/sponsor-dd/sponsor-bank-list/sponsor-bank-list.component.ts b/src/app/sponsor-dd/sponsor-bank-list/sponsor-bank-list.component.ts index 5a55ebe6c..1cf87d78a 100644 --- a/src/app/sponsor-dd/sponsor-bank-list/sponsor-bank-list.component.ts +++ b/src/app/sponsor-dd/sponsor-bank-list/sponsor-bank-list.component.ts @@ -20,6 +20,7 @@ import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; +import { AlertService } from 'app/core/alert/alert.service'; @Component({ selector: 'mifosx-sponsor-bank-list', @@ -356,6 +357,7 @@ export class AddSponsorBankDialogComponent implements OnInit { private http = inject(HttpClient); private dialogRef = inject(MatDialogRef); private snackBar = inject(MatSnackBar); + private alertService = inject(AlertService); /** The bank being edited, or null when adding a new one. */ data = inject(MAT_DIALOG_DATA, { optional: true }) as any; @@ -419,10 +421,24 @@ export class AddSponsorBankDialogComponent implements OnInit { ? this.http.put(`/sponsorbanks/${this.data.bank.id}`, this.form.value) : this.http.post('/sponsorbanks', this.form.value); request$.subscribe({ - next: () => { - this.snackBar.open(this.isEdit ? 'Sponsor Bank updated' : 'Sponsor Bank added successfully', 'Close', { - duration: 3000 - }); + next: (res: any) => { + // A create/update submitted for approval comes back HTTP 200 with rollbackTransaction: true - + // the bank has not actually been created/changed yet, so this must not claim success the same + // way an immediate create/update does. Routed through AlertService (not the local snackBar) + // so it uses the same top-right notification as the Payment Type/Working Days Maker-Checker + // flows, instead of the bottom-positioned snackbar used for this dialog's other messages. + if (res?.rollbackTransaction) { + this.alertService.alert({ + type: 'Success', + message: this.isEdit + ? 'Sponsor Bank update has been submitted for approval' + : 'Sponsor Bank creation has been submitted for approval' + }); + } else { + this.snackBar.open(this.isEdit ? 'Sponsor Bank updated' : 'Sponsor Bank added successfully', 'Close', { + duration: 3000 + }); + } this.dialogRef.close(true); }, error: (err) => { diff --git a/src/app/system/configure-maker-checker-tasks/configure-maker-checker-tasks.component.html b/src/app/system/configure-maker-checker-tasks/configure-maker-checker-tasks.component.html index 66fb9bf28..c2325820b 100644 --- a/src/app/system/configure-maker-checker-tasks/configure-maker-checker-tasks.component.html +++ b/src/app/system/configure-maker-checker-tasks/configure-maker-checker-tasks.component.html @@ -7,6 +7,26 @@ -->
+ + + @if (isSearchActive) { + + } +