diff --git a/src/app/pipes/date-format.pipe.ts b/src/app/pipes/date-format.pipe.ts index 4582f2621..c12113dff 100644 --- a/src/app/pipes/date-format.pipe.ts +++ b/src/app/pipes/date-format.pipe.ts @@ -59,6 +59,10 @@ export class DateFormatPipe implements PipeTransform, OnDestroy { if (Array.isArray(value)) { // Backend sometimes returns [yyyy, MM, dd] dateVal = moment(value.join('-'), 'YYYY-MM-DD'); + } else if (typeof value === 'number' && value < 1e12) { + // Unix timestamp in seconds (e.g. /makercheckers madeOnDate). + // moment(seconds) would treat it as milliseconds → 1970; use moment.unix() instead. + dateVal = moment.unix(value); } else { dateVal = moment(value); } diff --git a/src/app/products/products.service.ts b/src/app/products/products.service.ts index b696551cc..ea06b334b 100644 --- a/src/app/products/products.service.ts +++ b/src/app/products/products.service.ts @@ -87,7 +87,7 @@ export class ProductsService { /** * Submitting a savings product update (above) puts it in the pending-approval temp table instead of - * applying it directly. These calls drive the Approvals -> Pending Savings Product Edits screen. + * applying it directly. These calls drive the Approvals -> Product Approval screen. */ getPendingSavingsProductEdits(): Observable { return this.http.get('/savingsproducts/pending'); @@ -135,7 +135,7 @@ export class ProductsService { /** * Submitting a current product update (above) puts it in the pending-approval temp table instead of - * applying it directly. These calls drive the Approvals -> Pending Product Edits screen. + * applying it directly. These calls drive the Approvals -> Product Approval screen. */ getPendingCurrentProductEdits(): Observable { return this.http.get('/currentproducts/pending'); @@ -179,7 +179,7 @@ export class ProductsService { /** * Submitting a share product update (above) puts it in the pending-approval temp table instead of - * applying it directly. These calls drive the Approvals -> Pending Product Edits screen. + * applying it directly. These calls drive the Approvals -> Product Approval screen. */ getPendingShareProductEdits(): Observable { return this.http.get('/products/share/pending'); @@ -449,7 +449,7 @@ export class ProductsService { /** * Submitting a fixed deposit product update (above) puts it in the pending-approval temp table instead of - * applying it directly. These calls drive the Approvals -> Pending Product Edits screen. + * applying it directly. These calls drive the Approvals -> Product Approval screen. */ getPendingFixedDepositProductEdits(): Observable { return this.http.get('/fixeddepositproducts/pending'); @@ -744,7 +744,7 @@ export class ProductsService { /** * Submitting a recurring deposit product update (above) puts it in the pending-approval temp table instead of - * applying it directly. These calls drive the Approvals -> Pending Product Edits screen. + * applying it directly. These calls drive the Approvals -> Product Approval screen. */ getPendingRecurringDepositProductEdits(): Observable { return this.http.get('/recurringdepositproducts/pending'); diff --git a/src/app/savings/savings-account-view/transactions/view-transaction/savings-transaction-general-tab/savings-transaction-general-tab.component.ts b/src/app/savings/savings-account-view/transactions/view-transaction/savings-transaction-general-tab/savings-transaction-general-tab.component.ts index 99b8e30d5..2bc3398bc 100644 --- a/src/app/savings/savings-account-view/transactions/view-transaction/savings-transaction-general-tab/savings-transaction-general-tab.component.ts +++ b/src/app/savings/savings-account-view/transactions/view-transaction/savings-transaction-general-tab/savings-transaction-general-tab.component.ts @@ -27,7 +27,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; imports: [ ...STANDALONE_SHARED_IMPORTS, FaIconComponent, - NgClass, TransactionPaymentDetailComponent, CurrencyPipe, DateFormatPipe diff --git a/src/app/shared/accounting/view-journal-entry-transaction/view-journal-entry-transaction.component.ts b/src/app/shared/accounting/view-journal-entry-transaction/view-journal-entry-transaction.component.ts index 01047441e..fa6a999a2 100644 --- a/src/app/shared/accounting/view-journal-entry-transaction/view-journal-entry-transaction.component.ts +++ b/src/app/shared/accounting/view-journal-entry-transaction/view-journal-entry-transaction.component.ts @@ -60,7 +60,6 @@ import { MatTooltipModule } from '@angular/material/tooltip'; DateFormatPipe, DatetimeFormatPipe, FormatNumberPipe, - YesnoPipe, MatCardModule, MatTooltipModule ] diff --git a/src/app/shared/file-upload/file-upload.component.ts b/src/app/shared/file-upload/file-upload.component.ts index 5ad7607ca..583ce5cb2 100644 --- a/src/app/shared/file-upload/file-upload.component.ts +++ b/src/app/shared/file-upload/file-upload.component.ts @@ -20,9 +20,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; templateUrl: './file-upload.component.html', styleUrls: ['./file-upload.component.scss'], imports: [ - ...STANDALONE_SHARED_IMPORTS, - NgStyle, - FaIconComponent + ...STANDALONE_SHARED_IMPORTS ] }) export class FileUploadComponent implements OnInit { diff --git a/src/app/shared/keyboard-shortcuts-dialog/keyboard-shortcuts-dialog.component.ts b/src/app/shared/keyboard-shortcuts-dialog/keyboard-shortcuts-dialog.component.ts index d4c0ca1b5..685d2777b 100644 --- a/src/app/shared/keyboard-shortcuts-dialog/keyboard-shortcuts-dialog.component.ts +++ b/src/app/shared/keyboard-shortcuts-dialog/keyboard-shortcuts-dialog.component.ts @@ -42,10 +42,8 @@ import { MatOption } from '@angular/material/core'; MatDialogContent, MatDialogActions, MatDialogClose, - MatDivider, MatButton, FormsModule, - NgClass, MatIcon, MatFormField, MatLabel, diff --git a/src/app/shared/shortcut-usage-dialog/inline-create-shortcut-dialog.component.ts b/src/app/shared/shortcut-usage-dialog/inline-create-shortcut-dialog.component.ts index 741a0c065..aa08800eb 100644 --- a/src/app/shared/shortcut-usage-dialog/inline-create-shortcut-dialog.component.ts +++ b/src/app/shared/shortcut-usage-dialog/inline-create-shortcut-dialog.component.ts @@ -35,8 +35,7 @@ import { take } from 'rxjs/operators'; FormsModule, MatFormField, MatLabel, - MatInput, - MatError + MatInput ] }) export class InlineCreateShortcutDialogComponent { diff --git a/src/app/sponsor-dd-reconciliation/reco-batch-list/reco-batch-list.component.ts b/src/app/sponsor-dd-reconciliation/reco-batch-list/reco-batch-list.component.ts index 9bebd7216..a41050263 100644 --- a/src/app/sponsor-dd-reconciliation/reco-batch-list/reco-batch-list.component.ts +++ b/src/app/sponsor-dd-reconciliation/reco-batch-list/reco-batch-list.component.ts @@ -25,8 +25,7 @@ import { SponsorDDReconciliationService } from '../sponsor-dd-reconciliation.ser MatCardModule, MatTableModule, MatButtonModule, - MatSnackBarModule, - FaIconComponent + MatSnackBarModule ], templateUrl: './reco-batch-list.component.html', styles: [ diff --git a/src/app/sponsor-dd/dd-inventory-list/dd-inventory-list.component.ts b/src/app/sponsor-dd/dd-inventory-list/dd-inventory-list.component.ts index c1151416e..22da93780 100644 --- a/src/app/sponsor-dd/dd-inventory-list/dd-inventory-list.component.ts +++ b/src/app/sponsor-dd/dd-inventory-list/dd-inventory-list.component.ts @@ -34,8 +34,7 @@ import { SponsorDDService } from '../sponsor-dd.service'; MatTableModule, MatButtonModule, MatDialogModule, - MatSnackBarModule, - FaIconComponent + MatSnackBarModule ], templateUrl: './dd-inventory-list.component.html', styles: [ diff --git a/src/app/sponsor-dd/dd-list/dd-list.component.ts b/src/app/sponsor-dd/dd-list/dd-list.component.ts index 888679fd5..a8863798f 100644 --- a/src/app/sponsor-dd/dd-list/dd-list.component.ts +++ b/src/app/sponsor-dd/dd-list/dd-list.component.ts @@ -40,8 +40,7 @@ import { DividendClaimRequestService } from '../../clerk/dividend-claim-request. MatDialogModule, MatSnackBarModule, MatFormFieldModule, - MatInputModule, - FaIconComponent + MatInputModule ], templateUrl: './dd-list.component.html', styles: [ diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.ts index 7ed20e06d..e05e8aa64 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.ts +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/checker-inbox/checker-inbox.component.ts @@ -40,6 +40,7 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatCheckbox } from '@angular/material/checkbox'; import { DateFormatPipe } from '../../../pipes/date-format.pipe'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; +import { isProductEntity, isTempTableAccountEdit } from '../../product-entities'; @Component({ selector: 'mifosx-checker-inbox', @@ -105,27 +106,19 @@ export class CheckerInboxComponent implements OnInit { ]; /** - * Entities whose pending edits are gated by our own custom staging tables (m_savings_product_temp - * for products, the savings account edit-request table for Nominee/Digital-Feature edits) rather - * than the native command-source pipeline. They're excluded from Checker Inbox entirely - - * approving/rejecting them here would call the native /makercheckers/{id}?command=approve endpoint, - * which replays the original command handler directly and never touches our staging table, leaving - * its lock/hold stuck forever. Action them from Approvals -> Pending Product Edits / Pending Account - * Edits instead, where that's handled correctly. + * The Checker Inbox holds non-product pending requests only. Two groups are routed elsewhere: + * + * - Product requests of every action (CREATE, UPDATE, DELETE) belong to Approvals -> Product + * Approval, which merges them with the product edits staged in m_saving_product_temp so a + * checker sees one queue per product rather than two. + * - Savings/current account edits are staged the same way as product edits and belong to + * Approvals -> Pending Account Edits. Approving them here would call + * /makercheckers/{id}?command=approve, which replays the original command handler and never + * touches the staging table, leaving its lock held forever. */ - private readonly productEditEntities = [ - 'SAVINGSPRODUCT', - 'CURRENTPRODUCT', - 'SHAREPRODUCT', - 'FIXEDDEPOSITPRODUCT', - 'RECURRINGDEPOSITPRODUCT', - 'SAVINGSACCOUNT', - 'CURRENTACCOUNT' - ]; - - private excludeProductEdits(items: any[]): any[] { + private excludeRoutedElsewhere(items: any[]): any[] { return (items || []).filter( - (item: any) => !(this.productEditEntities.includes(item?.entityName) && item?.actionName === 'UPDATE') + (item: any) => !isProductEntity(item?.entityName) && !isTempTableAccountEdit(item?.entityName, item?.actionName) ); } @@ -142,7 +135,7 @@ export class CheckerInboxComponent implements OnInit { constructor() { this.route.data.subscribe((data: { makerCheckerResource: any; makerCheckerTemplate: any }) => { const raw = data.makerCheckerResource; - this.searchData = this.excludeProductEdits(Array.isArray(raw) ? raw : (raw?.pageItems ?? [])); + this.searchData = this.excludeRoutedElsewhere(Array.isArray(raw) ? raw : (raw?.pageItems ?? [])); this.checkerData = this.searchData.length > 0; this.makerCheckerTemplate = data.makerCheckerTemplate ?? {}; this.dataSource = new MatTableDataSource(this.searchData); @@ -182,7 +175,7 @@ export class CheckerInboxComponent implements OnInit { makerDateTimeto: this.dateUtils.formatDate(this.makerCheckerSearchForm.value.makerDateTimeto, dateFormat) }; this.tasksService.getMakerCheckerData(makerCheckerSearchParams).subscribe((response: any) => { - this.searchData = this.excludeProductEdits(response); + this.searchData = this.excludeRoutedElsewhere(response); if (this.searchData.length === 0) { this.noSearchedData = true; } else { @@ -290,11 +283,66 @@ export class CheckerInboxComponent implements OnInit { } /** - * Opens one inbox entry in a popup rather than navigating to its own page, so the checker keeps - * the list (and any search/filter they set up) behind the dialog. The direct route stays available - * for a bookmarked link — ViewCheckerInboxComponent supports both entry points. + * Maps a maker-checker entityName to the Angular route path for that entity's existing view page. + * Returns null for entity types that have no dedicated view page in this application. + */ + private entityRoute(entityName: string, resourceId: string | number): string | null { + const id = resourceId; + switch ((entityName ?? '').toUpperCase()) { + case 'LOANPRODUCT': + return `/products/loan-products/${id}`; + case 'SAVINGSPRODUCT': + return `/products/saving-products/${id}`; + case 'CURRENTPRODUCT': + return `/products/current-products/${id}`; + case 'SHAREPRODUCT': + return `/products/share-products/${id}`; + case 'FIXEDDEPOSITPRODUCT': + return `/products/fixed-deposit-products/${id}`; + case 'RECURRINGDEPOSITPRODUCT': + return `/products/recurring-deposit-products/${id}`; + case 'CHARGE': + return `/products/charges/${id}`; + case 'GLACCOUNT': + return `/accounting/chart-of-accounts/gl-accounts/view/${id}`; + case 'CLIENT': + return `/clients/${id}/general`; + case 'LOAN': + return `/loans/${id}`; + case 'SAVINGSACCOUNT': + return `/savings-accounts/${id}`; + case 'CURRENTACCOUNT': + return `/current-accounts/${id}`; + default: + return null; + } + } + + /** + * Handles a row click in the Checker Inbox. + * + * Priority: + * 1. If the row has a known entityName and a resourceId → navigate to the entity's existing + * view page (read-only; no maker-checker action buttons are present on those pages). + * 2. If no resourceId is available (e.g. a DELETE action where the entity no longer exists) + * → show a brief informational snackbar. + * 3. If the entityName is unrecognised → fall back to the existing ViewCheckerInboxComponent + * dialog so the checker can still view the command details and action the entry. */ viewEntry(row: any): void { + const entityName: string = row.entityName ?? ''; + const resourceId = row.resourceId; + const route = this.entityRoute(entityName, resourceId); + + if (route && resourceId != null) { + // Navigate to the existing entity view page (no maker-checker buttons there). + this.router.navigate([route]); + return; + } + + // resourceId is absent (CREATE rows: entity does not exist yet until approved), + // or entityName is unmapped — fall back to the detail dialog so the checker + // can still view the command payload and action the entry. this.dialog .open(ViewCheckerInboxComponent, { data: { checkerId: row.id }, diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-client-edits/pending-client-edits.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-client-edits/pending-client-edits.component.ts index 40ba63a9d..03f420097 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-client-edits/pending-client-edits.component.ts +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-client-edits/pending-client-edits.component.ts @@ -98,8 +98,7 @@ const NATIONALITY_LABELS: Record = { MatRowDef, MatRow, MatIcon, - MatPaginatorModule, - ClientEditEmailDialogComponent + MatPaginatorModule ] }) export class PendingClientEditsComponent implements OnInit { diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.html index ffccfe7d7..72145ac20 100644 --- a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.html +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.html @@ -11,7 +11,12 @@
search - +
@@ -20,7 +25,16 @@ Type - {{ edit.productType }} + {{ productTypeLabel(edit.productType) }} + + + + + Request + + + {{ edit.actionName }} + @@ -55,7 +69,12 @@ [disabled]="loadingDiffFor === edit" (click)="viewDiff(edit)" > - {{ loadingDiffFor === edit ? 'Loading…' : 'Compare' }} + + @if (loadingDiffFor === edit) { + Loading… + } @else { + {{ isPendingEdit(edit) ? 'Compare' : 'View' }} + }