From 17bfe4c7e64df7bdd838d24624d2b93eb45d6180 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Tue, 6 Oct 2026 10:56:25 +0530 Subject: [PATCH 1/2] fix(tasks): separate Pending Share Accounts from the Pending Account screen Removes the Pending Share Accounts section that had been bolted onto the Pending Account screen (duplicate data/actions alongside the normal-account table), restoring it as its own standalone queue. The standalone pending-share-accounts route/component already existed but had no tab link anywhere in the Approval Center nav, so it was unreachable from the UI - added a "Pending Share Accounts" tab for it. Co-Authored-By: Claude Sonnet 5 --- .../pending-account.component.html | 99 -------------- .../pending-account.component.scss | 10 -- .../pending-account.component.ts | 123 +----------------- .../checker-inbox-and-tasks.component.html | 9 ++ src/app/tasks/tasks-routing.module.ts | 5 +- 5 files changed, 13 insertions(+), 233 deletions(-) diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.html index b74942d53..c34911dca 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.html +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.html @@ -130,103 +130,4 @@

There are no accounts awaiting approval at this time.

} - - -

Pending Share Accounts

- @if (showShareData) { -
- - search - - -
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Account No.{{ account.accountNo }}Member - {{ account.clientName }} - @if (account.officeName) { -
{{ account.officeName }}
- } -
Product{{ account.productName }}Shares{{ account.requestedShares }}Amount - {{ account.requestedAmount | number: '1.2-2' }} - Resolution - {{ account.resolutionNumber || '—' }} - @if (account.resolutionDate) { -
{{ formatShareDate(account.resolutionDate) }}
- } -
Submitted - {{ formatShareDate(account.submittedDate) }} - @if (account.submittedByUsername) { -
by {{ account.submittedByUsername }}
- } -
Actions - - - -
-
- } - - @if (!showShareData) { -
- -

No pending share accounts

-

There are no share accounts awaiting approval at this time.

-
- } diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.scss index 2036b85b1..25eff1326 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.scss +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-account/pending-account.component.scss @@ -14,16 +14,6 @@ $text-muted: #64748b; padding: 16px 0; } -/* Marks where the Share Account section begins below the normal-account table on this same screen. */ -.pa-section-heading { - margin: 28px 0 12px; - font-size: 15px; - font-weight: 700; - color: #0d1b2a; - padding-top: 16px; - border-top: 1px solid $border; -} - .cv-action-bar { display: flex; align-items: center; 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 1726073c2..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 @@ -29,24 +29,17 @@ import { MatCheckbox } from '@angular/material/checkbox'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { TasksService } from '../../tasks.service'; -import { SharesService } from 'app/shares/shares.service'; import { SettingsService } from 'app/settings/settings.service'; import { Dates } from 'app/core/utils/dates'; -import { DecimalPipe } from '@angular/common'; -import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component'; import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component'; import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base'; import { DatepickerBase } from 'app/shared/form-dialog/formfield/model/datepicker-base'; -import { InputBase } from 'app/shared/form-dialog/formfield/model/input-base'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @Component({ selector: 'mifosx-pending-account', templateUrl: './pending-account.component.html', - styleUrls: [ - './pending-account.component.scss', - '../pending-share-accounts/pending-share-accounts.component.scss' - ], + styleUrls: ['./pending-account.component.scss'], imports: [ ...STANDALONE_SHARED_IMPORTS, FaIconComponent, @@ -61,8 +54,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; MatHeaderRow, MatRowDef, MatRow, - MatIcon, - DecimalPipe + MatIcon ] }) export class PendingAccountComponent { @@ -72,7 +64,6 @@ export class PendingAccountComponent { private dateUtils = inject(Dates); private settingsService = inject(SettingsService); private tasksService = inject(TasksService); - private sharesService = inject(SharesService); dataSource: MatTableDataSource; selection: SelectionModel; @@ -90,26 +81,8 @@ export class PendingAccountComponent { 'status' ]; - /** Share accounts awaiting approval - rendered as their own section on this same screen rather - * than a separate tab. Kept out of `dataSource`/`selection` (bulk-select + date-picker approve) - * since a share subscription is approved/rejected one at a time against today's business date, - * exactly as it always was on its own page - only where it's shown has changed. */ - shareDataSource: MatTableDataSource; - showShareData = false; - - shareDisplayedColumns: string[] = [ - 'accountNo', - 'clientName', - 'productName', - 'requestedShares', - 'requestedAmount', - 'resolution', - 'submittedDate', - 'actions' - ]; - constructor() { - this.route.data.subscribe((data: { pendingAccountsData: any; pendingShareAccountsData: any }) => { + this.route.data.subscribe((data: { pendingAccountsData: any }) => { // status 100 = submitted and pending approval only; exclude terminated accounts const items = (data.pendingAccountsData?.pageItems || []).filter( (a: any) => a.status?.id === 100 && !a.terminateTrue @@ -117,10 +90,6 @@ export class PendingAccountComponent { this.showData = items.length > 0; this.dataSource = new MatTableDataSource(items); this.selection = new SelectionModel(true, []); - - const shareItems = data.pendingShareAccountsData || []; - this.showShareData = shareItems.length > 0; - this.shareDataSource = new MatTableDataSource(shareItems); }); } @@ -314,92 +283,6 @@ export class PendingAccountComponent { return date; } - applyShareFilter(filterValue: string = '') { - this.shareDataSource.filter = filterValue.trim().toLowerCase(); - } - - /** A date from the share account API rendered readably, whether it arrives as a string or as - * Fineract's {@code [yyyy, m, d]} array. Kept separate from `formatDate()` above, which the - * normal-account table already relies on with its own date-array handling. */ - formatShareDate(date: any): string { - if (!date) { - return ''; - } - const d = Array.isArray(date) ? new Date(date[0], date[1] - 1, date[2]) : new Date(date); - return isNaN(d.getTime()) ? String(date) : d.toLocaleDateString('en-IN'); - } - - /** Opens the share account itself, for a checker who wants more than the row shows. */ - viewShareAccount(account: any): void { - this.router.navigate([ - '/clients', - account.clientId, - 'shares-accounts', - account.shareAccountId - ]); - } - - approveShareAccount(account: any): void { - const dialogRef = this.dialog.open(ConfirmationDialogComponent, { - data: { - heading: 'Approve Share Account', - dialogContext: - `Approve share account "${account.accountNo}" for ${account.clientName} — ` + - `${account.requestedShares} shares at ${account.requestedAmount}?`, - type: 'basic' - } - }); - dialogRef.afterClosed().subscribe((response: any) => { - if (response?.confirm) { - // The approval date is the business date rather than the checker's choice, same as this - // queue's original standalone screen: clearing a submission is a decision, not a backdated - // transaction, so a date field per row would be extra clicks for no benefit. - const data = { - approvedDate: this.dateUtils.formatDate(this.settingsService.businessDate, this.settingsService.dateFormat), - dateFormat: this.settingsService.dateFormat, - locale: this.settingsService.language.code - }; - this.sharesService - .executeSharesAccountCommand(account.shareAccountId, 'approve', data) - .subscribe(() => this.reload()); - } - }); - } - - rejectShareAccount(account: any): void { - const formfields: FormfieldBase[] = [ - new InputBase({ - controlName: 'rejectReason', - label: 'Reason for rejection', - value: '', - type: 'text', - required: false - }) - ]; - const dialogRef = this.dialog.open(FormDialogComponent, { - data: { - title: `Reject Share Account ${account.accountNo}`, - formfields - } - }); - dialogRef.afterClosed().subscribe((response: any) => { - if (response?.data) { - const data: any = { - rejectedDate: this.dateUtils.formatDate(this.settingsService.businessDate, this.settingsService.dateFormat), - dateFormat: this.settingsService.dateFormat, - locale: this.settingsService.language.code - }; - const reason = response.data.value?.rejectReason?.trim(); - if (reason) { - data.note = reason; - } - this.sharesService - .executeSharesAccountCommand(account.shareAccountId, 'reject', data) - .subscribe(() => this.reload()); - } - }); - } - reload() { const url = this.router.url; this.router diff --git a/src/app/tasks/checker-inbox-and-tasks/checker-inbox-and-tasks.component.html b/src/app/tasks/checker-inbox-and-tasks/checker-inbox-and-tasks.component.html index 79cea4c66..182b1f6c9 100644 --- a/src/app/tasks/checker-inbox-and-tasks/checker-inbox-and-tasks.component.html +++ b/src/app/tasks/checker-inbox-and-tasks/checker-inbox-and-tasks.component.html @@ -72,6 +72,15 @@ Pending Account + + + Pending Share Accounts + Date: Tue, 6 Oct 2026 12:39:43 +0530 Subject: [PATCH 2/2] fix(savings-product): guard against duplicate submission on create The product name is unique in the database, so a repeat click on Submit while the first request is still in flight fails with a duplicate key error even though the first request already created the product. Disables the button and ignores repeat calls until the request settles; also drops a stray debug console.log from the submit payload. Co-Authored-By: Claude Sonnet 5 --- .../create-saving-product.component.html | 2 +- .../create-saving-product.component.ts | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/app/products/saving-products/create-saving-product/create-saving-product.component.html b/src/app/products/saving-products/create-saving-product/create-saving-product.component.html index 867619fa8..f60a295f3 100644 --- a/src/app/products/saving-products/create-saving-product/create-saving-product.component.html +++ b/src/app/products/saving-products/create-saving-product/create-saving-product.component.html @@ -144,7 +144,7 @@ } @if (savingProductFormValid && selectedTabIndex === totalTabs - 1) { - diff --git a/src/app/products/saving-products/create-saving-product/create-saving-product.component.ts b/src/app/products/saving-products/create-saving-product/create-saving-product.component.ts index 942b0f195..7207b8d78 100644 --- a/src/app/products/saving-products/create-saving-product/create-saving-product.component.ts +++ b/src/app/products/saving-products/create-saving-product/create-saving-product.component.ts @@ -70,6 +70,10 @@ export class CreateSavingProductComponent { savingProductsTemplate: any; accountingRuleData: string[] = []; selectedTabIndex = 0; + /** Guards against repeat clicks on Submit: the product name is unique in the database, so a + * second request for the same form fails with a duplicate key error even though the first one + * created the product. */ + submitting = false; /** * @param {ActivatedRoute} route Activated Route. @@ -162,8 +166,11 @@ export class CreateSavingProductComponent { } submit() { + if (this.submitting) { + return; + } + this.submitting = true; const savingProduct = this.prepareSavingProductPayload(); - console.log('Saving product submit payload:', savingProduct); this.productsService.createSavingProduct(savingProduct).subscribe({ next: (response: any) => { if (response?.rollbackTransaction) { @@ -181,6 +188,11 @@ export class CreateSavingProductComponent { { relativeTo: this.route } ); } + }, + error: () => { + // the error itself is reported by the global error handler; just let the user correct the + // form and try again + this.submitting = false; } }); }