diff --git a/src/app/accounts/accounts.service.ts b/src/app/accounts/accounts.service.ts index fb262c0bc..ad7e49688 100644 --- a/src/app/accounts/accounts.service.ts +++ b/src/app/accounts/accounts.service.ts @@ -89,4 +89,8 @@ export class AccountsService { if (params.asOnDate) httpParams = httpParams.set('asOnDate', params.asOnDate); return this.http.get('/recurringdepositaccounts', { params: httpParams }); } + + terminateAccount(accountId: string, data: any): Observable { + return this.http.post(`/v1/accounts/${accountId}/terminate`, data); + } } diff --git a/src/app/cashier/cash-settlement/cash-settlement.component.html b/src/app/cashier/cash-settlement/cash-settlement.component.html index 7364fc900..cf3a97330 100644 --- a/src/app/cashier/cash-settlement/cash-settlement.component.html +++ b/src/app/cashier/cash-settlement/cash-settlement.component.html @@ -45,14 +45,21 @@ - - Teller - - @for (t of tellers; track t.id) { - {{ t.name }} - } - - + @if (tellers.length === 1) { + + Teller + + + } @else { + + Teller + + @for (t of tellers; track t.id) { + {{ t.name }} + } + + + } Cashier @@ -140,17 +147,24 @@ } - - Teller / Vault (To) * - - @for (t of tellers; track t.id) { - {{ t.name }} - } - @if (tellers.length === 0) { - No tellers configured - } - - + @if (tellers.length === 1) { + + Teller / Vault (To) * + + + } @else { + + Teller / Vault (To) * + + @for (t of tellers; track t.id) { + {{ t.name }} + } + @if (tellers.length === 0) { + No tellers configured + } + + + } Settlement Date diff --git a/src/app/cashier/cash-settlement/cash-settlement.component.ts b/src/app/cashier/cash-settlement/cash-settlement.component.ts index c93a31c64..45d5c0f93 100644 --- a/src/app/cashier/cash-settlement/cash-settlement.component.ts +++ b/src/app/cashier/cash-settlement/cash-settlement.component.ts @@ -97,6 +97,10 @@ export class CashSettlementComponent implements OnInit { .pipe(catchError(() => of([] as any[]))) .subscribe((list: any[]) => { this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name })); + if (this.tellers.length === 1) { + this.onTellerSelect(this.tellers[0].id); + this.onSupervisorTellerSelect(this.tellers[0].id); + } }); this.loadData(); } diff --git a/src/app/cashier/cash-transfer/cash-transfer.component.html b/src/app/cashier/cash-transfer/cash-transfer.component.html index d7dcbb54b..82cb5f963 100644 --- a/src/app/cashier/cash-transfer/cash-transfer.component.html +++ b/src/app/cashier/cash-transfer/cash-transfer.component.html @@ -17,17 +17,24 @@
- - From (Teller / Head) - - @for (t of tellers; track t.id) { - {{ t.name }} - } - @if (tellers.length === 0) { - No tellers configured - } - - + @if (tellers.length === 1) { + + From (Teller / Head) + + + } @else { + + From (Teller / Head) + + @for (t of tellers; track t.id) { + {{ t.name }} + } + @if (tellers.length === 0) { + No tellers configured + } + + + } To (Cashier) diff --git a/src/app/cashier/cash-transfer/cash-transfer.component.ts b/src/app/cashier/cash-transfer/cash-transfer.component.ts index ee3aa8a22..84f6ad450 100644 --- a/src/app/cashier/cash-transfer/cash-transfer.component.ts +++ b/src/app/cashier/cash-transfer/cash-transfer.component.ts @@ -95,6 +95,9 @@ export class CashTransferComponent implements OnInit { .pipe(catchError(() => of([] as any[]))) .subscribe((list) => { this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name })); + if (this.tellers.length === 1) { + this.onTellerSelect(this.tellers[0].id); + } }); } @@ -116,6 +119,9 @@ export class CashTransferComponent implements OnInit { .subscribe((r: any) => { this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName })); this.loadingCashiers = false; + if (this.cashiers.length === 1) { + this.onCashierSelect(this.cashiers[0].id); + } }); } diff --git a/src/app/cashier/cashier-position/cashier-position.component.html b/src/app/cashier/cashier-position/cashier-position.component.html index a7572fbf1..bc931fd1a 100644 --- a/src/app/cashier/cashier-position/cashier-position.component.html +++ b/src/app/cashier/cashier-position/cashier-position.component.html @@ -20,15 +20,22 @@
@if (!isCashierMode) { - - Teller (optional) - - All Tellers - @for (t of tellers; track t.id) { - {{ t.name }} - } - - + @if (tellers.length === 1) { + + Teller + + + } @else { + + Teller (optional) + + All Tellers + @for (t of tellers; track t.id) { + {{ t.name }} + } + + + } Cashier (optional) diff --git a/src/app/cashier/cashier-position/cashier-position.component.ts b/src/app/cashier/cashier-position/cashier-position.component.ts index 04f74efea..1ffa957b1 100644 --- a/src/app/cashier/cashier-position/cashier-position.component.ts +++ b/src/app/cashier/cashier-position/cashier-position.component.ts @@ -85,6 +85,9 @@ export class CashierPositionComponent implements OnInit { .pipe(catchError(() => of([] as any[]))) .subscribe((list) => { this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name })); + if (this.tellers.length === 1) { + this.onTellerSelect(this.tellers[0].id); + } }); } } @@ -137,6 +140,9 @@ export class CashierPositionComponent implements OnInit { .pipe(catchError(() => of({ cashiers: [] }))) .subscribe((r: any) => { this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName })); + if (this.cashiers.length === 1) { + this.selectedCashierId = this.cashiers[0].id; + } }); } diff --git a/src/app/cashier/inter-cashier-transfer/inter-cashier-transfer.component.html b/src/app/cashier/inter-cashier-transfer/inter-cashier-transfer.component.html index 742ad1269..b87a43472 100644 --- a/src/app/cashier/inter-cashier-transfer/inter-cashier-transfer.component.html +++ b/src/app/cashier/inter-cashier-transfer/inter-cashier-transfer.component.html @@ -17,17 +17,24 @@
- - Teller - - @for (t of tellers; track t.id) { - {{ t.name }} - } - @if (tellers.length === 0) { - No tellers configured - } - - + @if (tellers.length === 1) { + + Teller + + + } @else { + + Teller + + @for (t of tellers; track t.id) { + {{ t.name }} + } + @if (tellers.length === 0) { + No tellers configured + } + + + } From (Cashier) diff --git a/src/app/cashier/inter-cashier-transfer/inter-cashier-transfer.component.ts b/src/app/cashier/inter-cashier-transfer/inter-cashier-transfer.component.ts index f49f02895..047384c84 100644 --- a/src/app/cashier/inter-cashier-transfer/inter-cashier-transfer.component.ts +++ b/src/app/cashier/inter-cashier-transfer/inter-cashier-transfer.component.ts @@ -102,6 +102,9 @@ export class InterCashierTransferComponent implements OnInit { .pipe(catchError(() => of([] as any[]))) .subscribe((list) => { this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name })); + if (this.tellers.length === 1) { + this.onTellerSelect(this.tellers[0].id); + } }); } diff --git a/src/app/cashier/reconciliation/reconciliation.component.html b/src/app/cashier/reconciliation/reconciliation.component.html index 55036cb21..24c7280e0 100644 --- a/src/app/cashier/reconciliation/reconciliation.component.html +++ b/src/app/cashier/reconciliation/reconciliation.component.html @@ -20,14 +20,21 @@
- - Teller - - @for (t of tellers; track t.id) { - {{ t.name }} - } - - + @if (tellers.length === 1) { + + Teller + + + } @else { + + Teller + + @for (t of tellers; track t.id) { + {{ t.name }} + } + + + } Cashier diff --git a/src/app/cashier/reconciliation/reconciliation.component.ts b/src/app/cashier/reconciliation/reconciliation.component.ts index 27b8e3ff5..40e4eeae2 100644 --- a/src/app/cashier/reconciliation/reconciliation.component.ts +++ b/src/app/cashier/reconciliation/reconciliation.component.ts @@ -92,6 +92,9 @@ export class ReconciliationComponent implements OnInit { .pipe(catchError(() => of([] as any[]))) .subscribe((list) => { this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name })); + if (this.tellers.length === 1) { + this.onTellerSelect(this.tellers[0].id); + } }); this.loadHistory(); } @@ -108,6 +111,9 @@ export class ReconciliationComponent implements OnInit { .pipe(catchError(() => of({ cashiers: [] }))) .subscribe((r: any) => { this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName })); + if (this.cashiers.length === 1) { + this.onCashierSelect(this.cashiers[0].id); + } }); } diff --git a/src/app/core/shell/shell.component.html b/src/app/core/shell/shell.component.html index 9a38e1bcd..ba32a35ee 100644 --- a/src/app/core/shell/shell.component.html +++ b/src/app/core/shell/shell.component.html @@ -168,13 +168,6 @@ > My Deposits - - Share Collection - Cash History - diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html index 435884bcb..c040b6031 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.html @@ -155,12 +155,13 @@ -
- Share Linking Enabled - - {{ productTemplate.shareLinkingEnabled ? 'Yes' : 'No' }} - -
+ + Share Linking Enabled + + Yes + No + +
diff --git a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts index 6d0bc2920..51ab90f3f 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts +++ b/src/app/current-accounts/current-account-stepper/current-account-details-step/current-account-details-step.component.ts @@ -67,6 +67,8 @@ export class CurrentAccountDetailsStepComponent implements OnInit { introducerCif: string | null = null; private isEditMode = false; + shareLinkingEnabled = false; + // ── Non-Individual (existing) ────────────────────────────────────────── isNonIndividualClient = false; relatedClients: Array<{ @@ -280,6 +282,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit { this.currentAccountProductTemplate.emit(response); this.fieldOfficerData = response.fieldOfficerOptions; this.currentAccountProductSelected = true; + this.shareLinkingEnabled = !!response.shareLinkingEnabled; this.currentAccountDetailsForm.patchValue({ balanceSwapOption: !!response.balanceSwapOption, msme: !!response.msme, @@ -382,6 +385,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit { this.introducerName = ea.introducerName ?? null; this.introducerCif = ea.introducerCifNumber ?? null; } + this.shareLinkingEnabled = ea.shareLinkingEnabled !== undefined ? !!ea.shareLinkingEnabled : !!ea.isLinkingEnable; this.currentAccountDetailsForm.patchValue({ balanceSwapOption: !!ea.balanceSwapOption, msme: !!ea.msme, @@ -664,6 +668,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit { get currentAccountDetails() { const value = this.currentAccountDetailsForm.getRawValue(); + value.shareLinkingEnabled = this.shareLinkingEnabled; if (this.introducerId) { value.introducerId = this.introducerId; value.introducerName = this.introducerName; diff --git a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html index 12a853a84..9e07474ba 100644 --- a/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html +++ b/src/app/current-accounts/current-account-stepper/current-account-preview-step/current-account-preview-step.component.html @@ -131,9 +131,18 @@ Share Linking Enabled - {{ currentAccountProductTemplate?.shareLinkingEnabled | yesNo }} + {{ + (currentAccount?.shareLinkingEnabled !== undefined + ? currentAccount.shareLinkingEnabled + : currentAccountProductTemplate?.shareLinkingEnabled + ) | yesNo + }}
diff --git a/src/app/current-accounts/current-account-view/current-account-view.component.ts b/src/app/current-accounts/current-account-view/current-account-view.component.ts index ed398d664..3129db777 100644 --- a/src/app/current-accounts/current-account-view/current-account-view.component.ts +++ b/src/app/current-accounts/current-account-view/current-account-view.component.ts @@ -24,6 +24,9 @@ import { ClientsService } from 'app/clients/clients.service'; import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component'; import { Currency } from 'app/shared/models/general.model'; import { TranslateService } from '@ngx-translate/core'; +import { AccountsService } from 'app/accounts/accounts.service'; +import { SettingsService } from 'app/settings/settings.service'; +import { Dates } from 'app/core/utils/dates'; /** Environment Configuration */ import { environment } from '../../../environments/environment'; @@ -83,6 +86,9 @@ export class CurrentAccountViewComponent implements OnInit { private currentAccountsService = inject(CurrentAccountsService); private clientsService = inject(ClientsService); private translateService = inject(TranslateService); + private accountsService = inject(AccountsService); + private settingsService = inject(SettingsService); + private dateUtils = inject(Dates); dialog = inject(MatDialog); /** Current Account Data */ @@ -126,7 +132,8 @@ export class CurrentAccountViewComponent implements OnInit { Close: 'danger', Reject: 'danger', Delete: 'danger', - 'Withdrawn by Client': 'danger' + 'Withdrawn by Client': 'danger', + Terminate: 'danger' }; private readonly ACTION_ICON: Record = { @@ -157,7 +164,8 @@ export class CurrentAccountViewComponent implements OnInit { Close: 'times-circle', Reject: 'ban', Delete: 'trash', - 'Withdrawn by Client': 'user-slash' + 'Withdrawn by Client': 'user-slash', + Terminate: 'times-circle' }; kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' { @@ -309,6 +317,9 @@ export class CurrentAccountViewComponent implements OnInit { case 'Delete': this.deleteCurrentAccount(); break; + case 'Terminate': + this.terminateCurrentAccount(); + break; case 'Calculate Interest': this.calculateInterest(); break; @@ -370,6 +381,32 @@ export class CurrentAccountViewComponent implements OnInit { }); } + private terminateCurrentAccount() { + const terminateCurrentAccountDialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { + heading: 'Terminate Account', + dialogContext: 'Are you sure you want to terminate this account?', + type: 'Strong' + } + }); + terminateCurrentAccountDialogRef.afterClosed().subscribe((response: any) => { + if (response && response.confirm) { + const locale = this.settingsService.language.code; + const dateFormat = this.settingsService.dateFormat; + const terminatedOnDate = this.dateUtils.formatDate(new Date(), dateFormat); + const data = { + accountType: 'CURRENT', + terminatedOnDate, + dateFormat, + locale + }; + this.accountsService.terminateAccount(this.currentAccountData.id, data).subscribe(() => { + this.reload(); + }); + } + }); + } + /** * Calculates current account's interest */ diff --git a/src/app/current-accounts/current-account-view/current-accounts-buttons.config.ts b/src/app/current-accounts/current-account-view/current-accounts-buttons.config.ts index 9c33daef1..e5fb4df88 100644 --- a/src/app/current-accounts/current-account-view/current-accounts-buttons.config.ts +++ b/src/app/current-accounts/current-account-view/current-accounts-buttons.config.ts @@ -194,6 +194,10 @@ export class CurrentAccountButtonsConfiguration { { name: 'Delete', taskPermissionName: 'DELETE_CURRENTACCOUNT' + }, + { + name: 'Terminate', + taskPermissionName: 'DELETE_CURRENTACCOUNT' } ]; break; diff --git a/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.ts b/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.ts index 413186748..7399b0efa 100644 --- a/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.ts +++ b/src/app/deposits/fixed-deposits/edit-fixed-deposit-account/edit-fixed-deposit-account.component.ts @@ -87,7 +87,7 @@ export class EditFixedDepositAccountComponent { if (account?.depositPeriod) { this.selectedTenure = { depositPeriod: account.depositPeriod, - depositPeriodFrequencyId: account.depositPeriodFrequencyType?.id ?? 2, + depositPeriodFrequencyId: account.depositPeriodFrequency?.id ?? 2, depositAmount: account.depositAmount, isSeniorCitizen: !!account.isSeniorCitizen, appliedRate: account.seniorCitizenInterestRate ?? undefined diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component.ts index 76e81186b..447d132df 100644 --- a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component.ts +++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-currency-step/fixed-deposit-account-currency-step.component.ts @@ -50,6 +50,9 @@ export class FixedDepositAccountCurrencyStepComponent implements OnChanges { } if (Array.isArray(ea.nominees) && ea.nominees.length > 0) { this.nominees = [...ea.nominees]; + this.nominationFacility = true; + } else { + this.nominationFacility = false; } } if (changes['fixedDepositsAccountProductTemplate']?.currentValue && !this.existingAccountApplied) { diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposit-account-view.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposit-account-view.component.ts index 9365357c0..17faad6f1 100644 --- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposit-account-view.component.ts +++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposit-account-view.component.ts @@ -11,8 +11,8 @@ import { Component, OnInit, inject } from '@angular/core'; import { ActivatedRoute, Router, RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; -/** Custom Dialogs */ import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; +import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component'; import { CalculateInterestDialogComponent } from './custom-dialogs/calculate-interest-dialog/calculate-interest-dialog.component'; import { PostInterestDialogComponent } from './custom-dialogs/post-interest-dialog/post-interest-dialog.component'; import { ToggleWithholdTaxDialogComponent } from './custom-dialogs/toggle-withhold-tax-dialog/toggle-withhold-tax-dialog.component'; @@ -23,7 +23,10 @@ import { FixedDepositsButtonsConfiguration } from './fixed-deposits-buttons.conf /** Custom Services */ import { FixedDepositsService } from '../fixed-deposits.service'; import { SavingsService } from 'app/savings/savings.service'; +import { AccountsService } from 'app/accounts/accounts.service'; import { Currency } from 'app/shared/models/general.model'; +import { SettingsService } from 'app/settings/settings.service'; +import { Dates } from 'app/core/utils/dates'; import { MatTooltip } from '@angular/material/tooltip'; import { NgClass, CurrencyPipe } from '@angular/common'; import { AccountNumberComponent } from '../../../shared/account-number/account-number.component'; @@ -67,6 +70,9 @@ export class FixedDepositAccountViewComponent implements OnInit { private router = inject(Router); private fixedDepositsService = inject(FixedDepositsService); private savingsService = inject(SavingsService); + private accountsService = inject(AccountsService); + private settingsService = inject(SettingsService); + private dateUtils = inject(Dates); dialog = inject(MatDialog); /** Fixed Deposits Account Data */ @@ -99,7 +105,8 @@ export class FixedDepositAccountViewComponent implements OnInit { 'Apply Annual Fees': 'admin', Reject: 'danger', 'Withdrawn by Client': 'danger', - Delete: 'danger' + Delete: 'danger', + Terminate: 'danger' }; private readonly ACTION_ICON: Record = { @@ -121,7 +128,8 @@ export class FixedDepositAccountViewComponent implements OnInit { 'Apply Annual Fees': 'receipt', Reject: 'ban', 'Withdrawn by Client': 'user-slash', - Delete: 'trash' + Delete: 'trash', + Terminate: 'times-circle' }; kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' { @@ -237,6 +245,9 @@ export class FixedDepositAccountViewComponent implements OnInit { case 'Disable Withhold Tax': this.disableWithHoldTax(); break; + case 'Terminate': + this.terminateFixedDepositsAccount(); + break; } } @@ -256,6 +267,32 @@ export class FixedDepositAccountViewComponent implements OnInit { }); } + private terminateFixedDepositsAccount() { + const terminateFixedDepositAccountDialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { + heading: 'Terminate Account', + dialogContext: 'Are you sure you want to terminate this account?', + type: 'Strong' + } + }); + terminateFixedDepositAccountDialogRef.afterClosed().subscribe((response: any) => { + if (response && response.confirm) { + const locale = this.settingsService.language.code; + const dateFormat = this.settingsService.dateFormat; + const terminatedOnDate = this.dateUtils.formatDate(new Date(), dateFormat); + const data = { + accountType: 'FD', + terminatedOnDate, + dateFormat, + locale + }; + this.accountsService.terminateAccount(this.fixedDepositsAccountData.id, data).subscribe(() => { + this.reload(); + }); + } + }); + } + /** * Calculates fixed deposit account's interest */ diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts index b4a2ea5e0..271987c9e 100644 --- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts +++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/fixed-deposits-buttons.config.ts @@ -132,6 +132,9 @@ export class FixedDepositsButtonsConfiguration { }, { name: 'Delete' + }, + { + name: 'Terminate' } ]; break; diff --git a/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.ts b/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.ts index 702c8228e..4076b4661 100644 --- a/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.ts +++ b/src/app/deposits/recurring-deposits/edit-recurring-deposit-account/edit-recurring-deposit-account.component.ts @@ -86,7 +86,7 @@ export class EditRecurringDepositAccountComponent { // Pre-populate tenure from existing account if (account?.depositPeriod) { this.selectedTenureValue = account.depositPeriod; - this.selectedTenureFrequencyId = account.depositPeriodFrequencyType?.id ?? 2; + this.selectedTenureFrequencyId = account.depositPeriodFrequency?.id ?? 2; } if (account?.mandatoryRecommendedDepositAmount) { this.selectedDepositAmount = account.mandatoryRecommendedDepositAmount; diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-account-view.component.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-account-view.component.ts index d466d62bb..55c4deb69 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-account-view.component.ts +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-account-view.component.ts @@ -20,9 +20,13 @@ import { RecurringDepositsButtonsConfiguration } from './recurring-deposits-butt /** Custom Dialogs */ import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component'; +import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component'; import { RecurringDepositConfirmationDialogComponent } from './custom-dialogs/recurring-deposit-confirmation-dialog/recurring-deposit-confirmation-dialog.component'; import { Currency } from 'app/shared/models/general.model'; import { TranslateService } from '@ngx-translate/core'; +import { AccountsService } from 'app/accounts/accounts.service'; +import { SettingsService } from 'app/settings/settings.service'; +import { Dates } from 'app/core/utils/dates'; import { MatTooltip } from '@angular/material/tooltip'; import { NgClass, DecimalPipe, CurrencyPipe } from '@angular/common'; import { AccountNumberComponent } from '../../../shared/account-number/account-number.component'; @@ -67,6 +71,9 @@ export class RecurringDepositsAccountViewComponent implements OnInit { private router = inject(Router); private recurringDepositsService = inject(RecurringDepositsService); private savingsService = inject(SavingsService); + private accountsService = inject(AccountsService); + private settingsService = inject(SettingsService); + private dateUtils = inject(Dates); dialog = inject(MatDialog); private translateService = inject(TranslateService); @@ -103,7 +110,8 @@ export class RecurringDepositsAccountViewComponent implements OnInit { 'Apply Annual Fees': 'admin', Reject: 'danger', 'Withdrawn by Client': 'danger', - Delete: 'danger' + Delete: 'danger', + Terminate: 'danger' }; private readonly ACTION_ICON: Record = { @@ -125,7 +133,8 @@ export class RecurringDepositsAccountViewComponent implements OnInit { 'Apply Annual Fees': 'receipt', Reject: 'ban', 'Withdrawn by Client': 'user-slash', - Delete: 'trash' + Delete: 'trash', + Terminate: 'times-circle' }; kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' { @@ -287,6 +296,9 @@ export class RecurringDepositsAccountViewComponent implements OnInit { case 'Disable Withhold Tax': this.disableWithHoldTax(); break; + case 'Terminate': + this.terminateRecurringDepositsAccount(); + break; } } @@ -308,6 +320,32 @@ export class RecurringDepositsAccountViewComponent implements OnInit { }); } + private terminateRecurringDepositsAccount() { + const terminateRecurringDepositAccountDialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { + heading: 'Terminate Account', + dialogContext: 'Are you sure you want to terminate this account?', + type: 'Strong' + } + }); + terminateRecurringDepositAccountDialogRef.afterClosed().subscribe((response: any) => { + if (response && response.confirm) { + const locale = this.settingsService.language.code; + const dateFormat = this.settingsService.dateFormat; + const terminatedOnDate = this.dateUtils.formatDate(new Date(), dateFormat); + const data = { + accountType: 'RD', + terminatedOnDate, + dateFormat, + locale + }; + this.accountsService.terminateAccount(this.recurringDepositsAccountData.id, data).subscribe(() => { + this.reload(); + }); + } + }); + } + /** * Calculates savings account's interest */ diff --git a/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-buttons.config.ts b/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-buttons.config.ts index 5c18484c6..7e2f5dd5f 100644 --- a/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-buttons.config.ts +++ b/src/app/deposits/recurring-deposits/recurring-deposits-account-view/recurring-deposits-buttons.config.ts @@ -163,6 +163,11 @@ export class RecurringDepositsButtonsConfiguration { name: 'Delete', icon: 'trash', taskPermissionName: 'DELETE_RECURRINGDEPOSITACCOUNT' + }, + { + name: 'Terminate', + icon: 'times-circle', + taskPermissionName: 'DELETE_RECURRINGDEPOSITACCOUNT' } ]; break; diff --git a/src/app/organization/tellers/cashiers/cashiers.component.html b/src/app/organization/tellers/cashiers/cashiers.component.html index 02ff07016..6bafeab44 100644 --- a/src/app/organization/tellers/cashiers/cashiers.component.html +++ b/src/app/organization/tellers/cashiers/cashiers.component.html @@ -54,38 +54,6 @@ {{ cashier.isFullDay | yesNo }} - - {{ 'labels.inputs.Vault Actions' | translate }} - - - - - diff --git a/src/app/organization/tellers/cashiers/cashiers.component.ts b/src/app/organization/tellers/cashiers/cashiers.component.ts index f301ffc47..0e25d162c 100644 --- a/src/app/organization/tellers/cashiers/cashiers.component.ts +++ b/src/app/organization/tellers/cashiers/cashiers.component.ts @@ -67,8 +67,7 @@ export class CashiersComponent implements OnInit { displayedColumns: string[] = [ 'period', 'staffName', - 'isFullDay', - 'vaultActions' + 'isFullDay' ]; /** Data source for cashiers table. */ dataSource: MatTableDataSource; diff --git a/src/app/organization/tellers/tellers.component.html b/src/app/organization/tellers/tellers.component.html index 8e38d98f3..280588dc5 100644 --- a/src/app/organization/tellers/tellers.component.html +++ b/src/app/organization/tellers/tellers.component.html @@ -20,7 +20,7 @@ {{ 'labels.inputs.Filter' | translate }}
-
+
diff --git a/src/app/organization/tellers/tellers.component.ts b/src/app/organization/tellers/tellers.component.ts index fc3ea024e..1df141205 100644 --- a/src/app/organization/tellers/tellers.component.ts +++ b/src/app/organization/tellers/tellers.component.ts @@ -8,7 +8,6 @@ /** 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, @@ -55,7 +54,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; MatHeaderRow, MatRowDef, MatRow, - MatPaginator, DateFormatPipe ] }) @@ -75,8 +73,6 @@ export class TellersComponent implements OnInit { /** Data source for tellers table. */ dataSource: MatTableDataSource; - /** Paginator for tellers table. */ - @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator; /** Sorter for tellers table. */ @ViewChild(MatSort, { static: true }) sort: MatSort; @@ -110,7 +106,6 @@ export class TellersComponent implements OnInit { */ setTellers() { this.dataSource = new MatTableDataSource(this.tellersData); - this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort; } } diff --git a/src/app/pipes/status-lookup.pipe.ts b/src/app/pipes/status-lookup.pipe.ts index 33845febd..291729023 100644 --- a/src/app/pipes/status-lookup.pipe.ts +++ b/src/app/pipes/status-lookup.pipe.ts @@ -30,6 +30,7 @@ export class StatusLookupPipe implements PipeTransform { 'savingsAccountStatusType.activeInactive': 'status-active-overdue', 'savingsAccountStatusType.activeDormant': 'status-active-overdue', 'savingsAccountStatusType.matured': 'status-matured', + 'savingsAccountStatusType.terminated': 'status-deleted', 'shareAccountStatusType.submitted.and.pending.approval': 'status-pending', 'shareAccountStatusType.approved': 'status-approved', 'shareAccountStatusType.active': 'status-active', diff --git a/src/app/products/current-products/current-products.component.html b/src/app/products/current-products/current-products.component.html index f9f46dcb6..cecad78bb 100644 --- a/src/app/products/current-products/current-products.component.html +++ b/src/app/products/current-products/current-products.component.html @@ -135,12 +135,19 @@ diff --git a/src/app/products/fixed-deposit-products/edit-fixed-deposit-product/edit-fixed-deposit-product.component.ts b/src/app/products/fixed-deposit-products/edit-fixed-deposit-product/edit-fixed-deposit-product.component.ts index a02ad4d3d..7bad586e3 100644 --- a/src/app/products/fixed-deposit-products/edit-fixed-deposit-product/edit-fixed-deposit-product.component.ts +++ b/src/app/products/fixed-deposit-products/edit-fixed-deposit-product/edit-fixed-deposit-product.component.ts @@ -274,7 +274,8 @@ export class EditFixedDepositProductComponent { ...this.fixedDepositProduct, charts: this.fixedDepositProductCharts, charges: (this.fixedDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })), - locale: this.settingsService.language.code + locale: this.settingsService.language.code, + dateFormat: this.settingsService.dateFormat }); delete fixedDepositProduct.advancedAccountingRules; this.productsService diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-products.component.html b/src/app/products/fixed-deposit-products/fixed-deposit-products.component.html index 6659f570b..77146aba1 100644 --- a/src/app/products/fixed-deposit-products/fixed-deposit-products.component.html +++ b/src/app/products/fixed-deposit-products/fixed-deposit-products.component.html @@ -137,12 +137,19 @@ diff --git a/src/app/products/products.service.ts b/src/app/products/products.service.ts index d9a8e4745..c986955bc 100644 --- a/src/app/products/products.service.ts +++ b/src/app/products/products.service.ts @@ -74,6 +74,26 @@ export class ProductsService { return this.http.put(`/savingsproducts/${savingProductId}`, savingProduct); } + /** + * 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. + */ + getPendingSavingsProductEdits(): Observable { + return this.http.get('/savingsproducts/pending'); + } + + getPendingSavingsProductEdit(savingsProductId: string): Observable { + return this.http.get(`/savingsproducts/${savingsProductId}/pending`); + } + + approveSavingsProductEdit(savingsProductId: string): Observable { + return this.http.post(`/savingsproducts/${savingsProductId}/approve`, {}); + } + + rejectSavingsProductEdit(savingsProductId: string, rejectReason: string): Observable { + return this.http.post(`/savingsproducts/${savingsProductId}/reject`, { rejectReason }); + } + /** * @returns {Observable} Current products data */ @@ -102,6 +122,26 @@ export class ProductsService { return this.http.delete(`/currentproducts/${currentProductId}`); } + /** + * 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. + */ + getPendingCurrentProductEdits(): Observable { + return this.http.get('/currentproducts/pending'); + } + + getPendingCurrentProductEdit(currentProductId: string): Observable { + return this.http.get(`/currentproducts/${currentProductId}/pending`); + } + + approveCurrentProductEdit(currentProductId: string): Observable { + return this.http.post(`/currentproducts/${currentProductId}/approve`, {}); + } + + rejectCurrentProductEdit(currentProductId: string, rejectReason: string): Observable { + return this.http.post(`/currentproducts/${currentProductId}/reject`, { rejectReason }); + } + /** * @returns {Observable} Share products data */ @@ -126,6 +166,26 @@ export class ProductsService { return this.http.put(`/products/share/${shareProductId}`, shareProduct); } + /** + * 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. + */ + getPendingShareProductEdits(): Observable { + return this.http.get('/products/share/pending'); + } + + getPendingShareProductEdit(shareProductId: string): Observable { + return this.http.get(`/products/share/${shareProductId}/pending`); + } + + approveShareProductEdit(shareProductId: string): Observable { + return this.http.post(`/products/share/${shareProductId}/approve`, {}); + } + + rejectShareProductEdit(shareProductId: string, rejectReason: string): Observable { + return this.http.post(`/products/share/${shareProductId}/reject`, { rejectReason }); + } + getDividends(shareProductId: string): Observable { return this.http.get(`/shareproduct/${shareProductId}/dividend`); } @@ -251,6 +311,26 @@ export class ProductsService { return this.http.put(`/fixeddepositproducts/${fixedDepositProductId}`, fixedDepositProduct); } + /** + * 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. + */ + getPendingFixedDepositProductEdits(): Observable { + return this.http.get('/fixeddepositproducts/pending'); + } + + getPendingFixedDepositProductEdit(fixedDepositProductId: string): Observable { + return this.http.get(`/fixeddepositproducts/${fixedDepositProductId}/pending`); + } + + approveFixedDepositProductEdit(fixedDepositProductId: string): Observable { + return this.http.post(`/fixeddepositproducts/${fixedDepositProductId}/approve`, {}); + } + + rejectFixedDepositProductEdit(fixedDepositProductId: string, rejectReason: string): Observable { + return this.http.post(`/fixeddepositproducts/${fixedDepositProductId}/reject`, { rejectReason }); + } + /** * @returns {Observable} Tax Components data */ @@ -526,6 +606,26 @@ export class ProductsService { return this.http.put(`/recurringdepositproducts/${recurringDepositProductId}`, recurringDepositProduct); } + /** + * 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. + */ + getPendingRecurringDepositProductEdits(): Observable { + return this.http.get('/recurringdepositproducts/pending'); + } + + getPendingRecurringDepositProductEdit(recurringDepositProductId: string): Observable { + return this.http.get(`/recurringdepositproducts/${recurringDepositProductId}/pending`); + } + + approveRecurringDepositProductEdit(recurringDepositProductId: string): Observable { + return this.http.post(`/recurringdepositproducts/${recurringDepositProductId}/approve`, {}); + } + + rejectRecurringDepositProductEdit(recurringDepositProductId: string, rejectReason: string): Observable { + return this.http.post(`/recurringdepositproducts/${recurringDepositProductId}/reject`, { rejectReason }); + } + /** Returns the active CBS base interest-rate version(s). */ getAllRdInterestRates(): Observable { return this.getRdBaseInterestRates(); diff --git a/src/app/products/recurring-deposit-products/edit-recurring-deposit-product/edit-recurring-deposit-product.component.ts b/src/app/products/recurring-deposit-products/edit-recurring-deposit-product/edit-recurring-deposit-product.component.ts index 595b959f3..b3efe62b1 100644 --- a/src/app/products/recurring-deposit-products/edit-recurring-deposit-product/edit-recurring-deposit-product.component.ts +++ b/src/app/products/recurring-deposit-products/edit-recurring-deposit-product/edit-recurring-deposit-product.component.ts @@ -176,7 +176,11 @@ export class EditRecurringDepositProductComponent { ...this.recurringDepositProductSettingsStep.recurringDepositProductSettings, ...this.recurringDepositProductChargesStep.recurringDepositProductCharges, ...this.recurringDepositProductAccountingStep.recurringDepositProductAccounting, - charts: this.recurringDepositProductCharts, + // charts intentionally omitted here - the backend no longer requires a charts payload for RD + // updates once rdSlabs is present, and this getter's chart data comes from the shared CBS + // base-rate template (not this product's own chart), which caused spurious + // "chart.overlapping.from.and.end.dates" errors when a product's real chart had different + // dates. Still included in recurringDepositProductPreview below for the read-only display tab. rdSlabs: this.activeBaseSlabs }; } @@ -240,9 +244,9 @@ export class EditRecurringDepositProductComponent { submit() { const recurringDepositProduct: any = this.pruneEmptyValues({ ...this.recurringDepositProduct, - charts: this.recurringDepositProductCharts, charges: (this.recurringDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })), - locale: this.settingsService.language.code + locale: this.settingsService.language.code, + dateFormat: this.settingsService.dateFormat }); if (!recurringDepositProduct.description) { recurringDepositProduct.description = ''; diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.ts b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.ts index 41d2d93cc..3ceaa248a 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.ts +++ b/src/app/products/recurring-deposit-products/recurring-deposit-product-stepper/recurring-deposit-product-accounting-step/recurring-deposit-product-accounting-step.component.ts @@ -91,7 +91,11 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit { otherGlMappingsMap: this.recurringDepositProductsTemplate?.otherGlMappingsMap }; const accountingMappingOptions = this.recurringDepositProductsTemplate?.accountingMappingOptions || {}; - const accountingRuleId = 2; // Always Cash + // Defaults to Cash (2) for a brand-new product (no existing accountingRule yet), but preserves + // the product's own current accounting rule when editing - previously this was hardcoded to 2 + // unconditionally, which silently forced every edited product onto Cash-based accounting (and + // its required GL mappings) even for products that were and should remain "None". + const accountingRuleId = this.recurringDepositProductsTemplate?.accountingRule?.id ?? 2; this.assetAccountOptions = accountingMappingOptions.assetAccountOptions || []; this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || []; diff --git a/src/app/products/recurring-deposit-products/recurring-deposit-products.component.html b/src/app/products/recurring-deposit-products/recurring-deposit-products.component.html index 7616d87e5..62b3fc3d7 100644 --- a/src/app/products/recurring-deposit-products/recurring-deposit-products.component.html +++ b/src/app/products/recurring-deposit-products/recurring-deposit-products.component.html @@ -147,12 +147,19 @@ diff --git a/src/app/products/saving-products/saving-products.component.html b/src/app/products/saving-products/saving-products.component.html index 2ec3107a5..356b8d2c1 100644 --- a/src/app/products/saving-products/saving-products.component.html +++ b/src/app/products/saving-products/saving-products.component.html @@ -145,13 +145,19 @@ diff --git a/src/app/products/saving-products/saving-products.component.scss b/src/app/products/saving-products/saving-products.component.scss index a379b31c9..c54aa32d3 100644 --- a/src/app/products/saving-products/saving-products.component.scss +++ b/src/app/products/saving-products/saving-products.component.scss @@ -481,6 +481,14 @@ border-color: var(--sp-amber); } } + + &--disabled, + &--disabled:hover { + background: #f1f5f9; + color: #94a3b8; + border-color: #e2e8f0; + cursor: not-allowed; + } } /* No match strip */ diff --git a/src/app/products/share-products/share-products.component.html b/src/app/products/share-products/share-products.component.html index c44b89651..db28741c4 100644 --- a/src/app/products/share-products/share-products.component.html +++ b/src/app/products/share-products/share-products.component.html @@ -135,12 +135,19 @@ diff --git a/src/app/products/share-products/share-products.component.ts b/src/app/products/share-products/share-products.component.ts index b01a42ab1..f98f40874 100644 --- a/src/app/products/share-products/share-products.component.ts +++ b/src/app/products/share-products/share-products.component.ts @@ -28,6 +28,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router'; /** Custom Services */ import { PopoverService } from '../../configuration-wizard/popover/popover.service'; import { ConfigurationWizardService } from '../../configuration-wizard/configuration-wizard.service'; +import { ProductsService } from 'app/products/products.service'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { MatTooltip } from '@angular/material/tooltip'; import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; @@ -60,6 +61,7 @@ export class ShareProductsComponent implements OnInit, AfterViewInit { private router = inject(Router); private configurationWizardService = inject(ConfigurationWizardService); private popoverService = inject(PopoverService); + private productsService = inject(ProductsService); shareProductsData: any[] = []; displayedColumns: string[] = [ @@ -101,6 +103,18 @@ export class ShareProductsComponent implements OnInit, AfterViewInit { constructor() { this.route.data.subscribe((data: { shareProducts: any }) => { this.shareProductsData = data.shareProducts.pageItems; + // ShareProductData is immutable on the backend, so isPendingApproval isn't embedded per-row like the + // other product lists - cross-reference against the pending-edits list instead. + this.productsService.getPendingShareProductEdits().subscribe((pendingEdits: any[]) => { + const pendingProductIds = new Set((pendingEdits || []).map((e) => e.savingsProductId)); + this.shareProductsData = this.shareProductsData.map((p) => ({ + ...p, + isPendingApproval: pendingProductIds.has(p.id) + })); + this.dataSource = new MatTableDataSource(this.shareProductsData); + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + }); }); } diff --git a/src/app/savings/create-savings-account/create-savings-account.component.html b/src/app/savings/create-savings-account/create-savings-account.component.html index 56d77ee1c..7bdf5abed 100644 --- a/src/app/savings/create-savings-account/create-savings-account.component.html +++ b/src/app/savings/create-savings-account/create-savings-account.component.html @@ -117,6 +117,7 @@ [savingsAccount]="savingsAccount" [savingsAccountDigital]="savingsAccountDigital" [savingsAccountNominees]="savingsAccountCurrencyStep?.nominees ?? []" + [submitting]="submitting" (submitEvent)="submit()" > diff --git a/src/app/savings/create-savings-account/create-savings-account.component.ts b/src/app/savings/create-savings-account/create-savings-account.component.ts index e7657c1ca..e5d7c2989 100644 --- a/src/app/savings/create-savings-account/create-savings-account.component.ts +++ b/src/app/savings/create-savings-account/create-savings-account.component.ts @@ -64,6 +64,8 @@ export class CreateSavingsAccountComponent { selectedTabIndex = 0; /** Total wizard tabs — Product, Currency, Digital, Preview. */ private readonly totalTabs = 4; + /** Guards against duplicate submissions from a double-click while the create request is in flight. */ + submitting = false; @ViewChild(SavingsAccountDetailsStepComponent, { static: true }) savingsAccountDetailsStep: SavingsAccountDetailsStepComponent; @@ -116,6 +118,9 @@ export class CreateSavingsAccountComponent { } submit() { + if (this.submitting) { + return; + } const detailsStep = this.savingsAccountDetailsStep; if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) { this.alertService.alert({ @@ -177,6 +182,7 @@ export class CreateSavingsAccountComponent { })); } + this.submitting = true; this.savingsService.createSavingsAccount(savingsAccount).subscribe({ next: (response: any) => { this.router.navigate( @@ -188,6 +194,7 @@ export class CreateSavingsAccountComponent { ); }, error: (error: any) => { + this.submitting = false; const errorCode = error?.error?.errors?.[0]?.userMessageGlobalisationCode ?? error?.error?.userMessageGlobalisationCode; if (errorCode === 'error.msg.savings.account.duplicate.product.for.client') { diff --git a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html index 0e2834bc4..ea68c5170 100644 --- a/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html +++ b/src/app/savings/savings-account-stepper/savings-account-details-step/savings-account-details-step.component.html @@ -1,4 +1,4 @@ - + +
+ @if (showData) { +
+ + search + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Type + {{ edit.productType }} + Product{{ edit.savingsProductName }}Submitted By{{ edit.makerUsername }}Submitted On{{ formatDate(edit.createdOn) }}Status + {{ edit.status }} + Actions + + + +
+
+ + @if (selectedEdit) { +
+
+

Comparing "{{ selectedEdit.savingsProductName }}" — Original vs Proposed

+ +
+ @if (diffRows.length === 0) { +

Loading comparison…

+ } @else { + + + + + + + + + + @for (row of diffRows; track row.label) { + + + + + + } + +
FieldOriginalProposed
{{ row.label }}{{ row.original }}{{ row.proposed }}
+ } +
+ } + } + + @if (!showData) { +
+ +

No pending savings product edits

+

There are no savings product edits awaiting approval at this time.

+
+ } +
diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.scss new file mode 100644 index 000000000..892bb22f2 --- /dev/null +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.scss @@ -0,0 +1,180 @@ +/** + * 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; +$text-muted: #64748b; + +.psp-tab-content { + padding: 16px 0; +} + +.psp-action-bar { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + flex-wrap: wrap; +} + +.psp-search-field { + flex: 1; + min-width: 200px; + max-width: 320px; +} + +.psp-btn { + font-size: 11.5px; + font-weight: 600; + border-radius: 8px !important; + height: 32px; + padding: 0 10px; + margin-right: 6px; + + fa-icon { + margin-right: 4px; + font-size: 11px; + } +} + +.psp-btn-view { + border-color: #2563eb; + color: #2563eb; + + &:hover { + background: #eff6ff; + } +} + +.psp-btn-approve { + border-color: #16a34a; + color: #16a34a; + + &:hover { + background: #f0fdf4; + } +} + +.psp-btn-reject { + border-color: #dc2626; + color: #dc2626; + + &:hover { + background: #fef2f2; + } +} + +.status-badge { + display: inline-flex; + align-items: center; + padding: 2px 10px; + border-radius: 20px; + font-size: 11px; + font-weight: 600; + + &--pending { + background: #fef9c3; + color: #854d0e; + } + + &--type { + background: #e0e7ff; + color: #3730a3; + } +} + +.psp-diff-panel { + margin-top: 20px; + border: 1px solid $border; + border-radius: 12px; + background: #fff; + padding: 16px; +} + +.psp-diff-header { + display: flex; + align-items: center; + justify-content: space-between; + + h3 { + margin: 0; + font-size: 14px; + font-weight: 700; + } +} + +.psp-diff-loading { + color: $text-muted; + font-size: 13px; +} + +.psp-diff-table { + width: 100%; + border-collapse: collapse; + margin-top: 12px; + font-size: 12.5px; + + th, + td { + text-align: left; + padding: 8px 10px; + border-bottom: 1px solid $border; + } + + th { + color: $text-muted; + font-weight: 600; + } +} + +.psp-diff-row--changed { + background: #fffbeb; + + .psp-diff-original { + color: #b91c1c; + text-decoration: line-through; + } + + .psp-diff-proposed { + color: #15803d; + font-weight: 600; + } +} + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 24px; + background: #fff; + border: 1px dashed $border; + border-radius: 14px; + text-align: center; + margin: 16px 0; + + .empty-icon { + font-size: 42px; + color: #cbd5e1; + margin-bottom: 14px; + } + + .empty-title { + margin: 0 0 4px; + font-size: 15px; + font-weight: 700; + color: #0d1b2a; + } + + .empty-text { + margin: 0; + font-size: 12.5px; + color: $text-muted; + max-width: 380px; + line-height: 1.5; + } +} diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.ts new file mode 100644 index 000000000..db9554f9d --- /dev/null +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component.ts @@ -0,0 +1,217 @@ +/** + * 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, inject } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { + MatTableDataSource, + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow +} from '@angular/material/table'; +import { MatDialog } from '@angular/material/dialog'; +import { MatIcon } from '@angular/material/icon'; +import { MatIconButton } from '@angular/material/button'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; + +import { ProductsService } from 'app/products/products.service'; +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 { InputBase } from 'app/shared/form-dialog/formfield/model/input-base'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-pending-savings-products', + templateUrl: './pending-savings-products.component.html', + styleUrls: ['./pending-savings-products.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FaIconComponent, + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow, + MatIcon, + MatIconButton + ] +}) +export class PendingSavingsProductsComponent { + private route = inject(ActivatedRoute); + private router = inject(Router); + private dialog = inject(MatDialog); + private productsService = inject(ProductsService); + + dataSource: MatTableDataSource; + showData = false; + + displayedColumns: string[] = [ + 'productType', + 'savingsProductName', + 'makerUsername', + 'createdOn', + 'status', + 'actions' + ]; + + selectedEdit: any = null; + originalProduct: any = null; + diffRows: { label: string; original: any; proposed: any; changed: boolean }[] = []; + + constructor() { + this.route.data.subscribe((data: { pendingSavingsProductsData: any }) => { + const items = data.pendingSavingsProductsData || []; + this.showData = items.length > 0; + this.dataSource = new MatTableDataSource(items); + }); + } + + applyFilter(filterValue: string = '') { + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + formatDate(date: any): string { + if (!date) return ''; + const d = Array.isArray(date) + ? new Date(date[0], date[1] - 1, date[2], date[3] || 0, date[4] || 0) + : new Date(date); + return isNaN(d.getTime()) ? String(date) : d.toLocaleString('en-IN'); + } + + private getOriginalProduct$(edit: any) { + switch (edit.productType) { + case 'CURRENT': + return this.productsService.getCurrentProduct(edit.savingsProductId); + case 'SHARE': + return this.productsService.getShareProduct(edit.savingsProductId); + case 'FD': + return this.productsService.getFixedDepositProduct(edit.savingsProductId); + case 'RD': + return this.productsService.getRecurringDepositProduct(edit.savingsProductId); + default: + return this.productsService.getSavingProduct(edit.savingsProductId); + } + } + + private approveEdit$(edit: any) { + switch (edit.productType) { + case 'CURRENT': + return this.productsService.approveCurrentProductEdit(edit.savingsProductId); + case 'SHARE': + return this.productsService.approveShareProductEdit(edit.savingsProductId); + case 'FD': + return this.productsService.approveFixedDepositProductEdit(edit.savingsProductId); + case 'RD': + return this.productsService.approveRecurringDepositProductEdit(edit.savingsProductId); + default: + return this.productsService.approveSavingsProductEdit(edit.savingsProductId); + } + } + + private rejectEdit$(edit: any, rejectReason: string) { + switch (edit.productType) { + case 'CURRENT': + return this.productsService.rejectCurrentProductEdit(edit.savingsProductId, rejectReason); + case 'SHARE': + return this.productsService.rejectShareProductEdit(edit.savingsProductId, rejectReason); + case 'FD': + return this.productsService.rejectFixedDepositProductEdit(edit.savingsProductId, rejectReason); + case 'RD': + return this.productsService.rejectRecurringDepositProductEdit(edit.savingsProductId, rejectReason); + default: + return this.productsService.rejectSavingsProductEdit(edit.savingsProductId, rejectReason); + } + } + + viewDiff(edit: any): void { + this.selectedEdit = edit; + this.diffRows = []; + this.getOriginalProduct$(edit).subscribe((original: any) => { + this.originalProduct = original; + const proposed = edit.proposedChanges || {}; + const keys = Object.keys(proposed); + this.diffRows = keys.map((key) => { + const originalValue = original ? original[key] : undefined; + const proposedValue = proposed[key]; + return { + label: key, + original: originalValue, + proposed: proposedValue, + changed: JSON.stringify(originalValue) !== JSON.stringify(proposedValue) + }; + }); + }); + } + + closeDiff(): void { + this.selectedEdit = null; + this.originalProduct = null; + this.diffRows = []; + } + + approve(edit: any): void { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { + heading: 'Approve Savings Product Edit', + dialogContext: `Apply the pending edit to "${edit.savingsProductName}"? This cannot be undone.`, + type: 'basic' + } + }); + dialogRef.afterClosed().subscribe((response: any) => { + if (response?.confirm) { + this.approveEdit$(edit).subscribe(() => this.reload()); + } + }); + } + + reject(edit: 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 edit for "${edit.savingsProductName}"`, + layout: { addButtonText: 'Reject' }, + formfields + } + }); + dialogRef.afterClosed().subscribe((response: any) => { + if (response?.data) { + const rejectReason = response.data.value.rejectReason || ''; + this.rejectEdit$(edit, rejectReason).subscribe(() => this.reload()); + } + }); + } + + reload(): void { + this.closeDiff(); + const url = this.router.url; + this.router + .navigateByUrl('/checker-inbox-and-tasks', { skipLocationChange: true }) + .then(() => this.router.navigate([url])); + } +} diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.html b/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.html new file mode 100644 index 000000000..e9142a221 --- /dev/null +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.html @@ -0,0 +1,75 @@ + + +
+ @if (showData) { +
+ + search + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Account No{{ row.accountNo }}Client Name{{ row.clientName }}Product{{ row.savingsProductName || row.productName }}Type{{ row.accountType }}Office{{ row.officeName }}Submitted On{{ formatDate(row.timeline?.submittedOnDate || row.submittedOnDate) }}Status + Terminated +
+
+ } + + @if (!showData) { +
+ +

No terminated accounts

+

Accounts marked for termination will appear here.

+
+ } +
diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.scss b/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.scss new file mode 100644 index 000000000..a573feda6 --- /dev/null +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.scss @@ -0,0 +1,17 @@ +/** + * 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/. + */ + +.status-badge--terminated { + background: #fef2f2; + color: #dc2626; + border: 1px solid #fecaca; + padding: 2px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; +} diff --git a/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.ts b/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.ts new file mode 100644 index 000000000..e74e61ec2 --- /dev/null +++ b/src/app/tasks/checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component.ts @@ -0,0 +1,106 @@ +/** + * 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, inject } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { + MatTableDataSource, + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow +} from '@angular/material/table'; +import { MatIcon } from '@angular/material/icon'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module'; + +@Component({ + selector: 'mifosx-terminated-account', + templateUrl: './terminated-account.component.html', + styleUrls: ['./terminated-account.component.scss'], + imports: [ + ...STANDALONE_SHARED_IMPORTS, + FaIconComponent, + MatTable, + MatColumnDef, + MatHeaderCellDef, + MatHeaderCell, + MatCellDef, + MatCell, + MatHeaderRowDef, + MatHeaderRow, + MatRowDef, + MatRow, + MatIcon + ] +}) +export class TerminatedAccountComponent { + private route = inject(ActivatedRoute); + private router = inject(Router); + + dataSource: MatTableDataSource; + showData = false; + + displayedColumns: string[] = [ + 'accountNo', + 'clientName', + 'productName', + 'accountType', + 'officeName', + 'submittedOnDate', + 'status' + ]; + + constructor() { + this.route.data.subscribe((data: { terminatedAccountsData: any }) => { + const items = data.terminatedAccountsData?.pageItems || []; + this.showData = items.length > 0; + this.dataSource = new MatTableDataSource(items); + }); + } + + navigateToAccount(account: any): void { + const clientId = account.clientId; + const accountId = account.id; + const depositTypeId = account.depositType?.id; + if (account.accountType === 'Current') { + this.router.navigate([`/clients/${clientId}/current-accounts/${accountId}`]); + } else if (depositTypeId === 200) { + this.router.navigate([`/clients/${clientId}/fixed-deposits-accounts/${accountId}`]); + } else if (depositTypeId === 300) { + this.router.navigate([`/clients/${clientId}/recurring-deposits-accounts/${accountId}`]); + } else { + this.router.navigate([`/clients/${clientId}/savings-accounts/${accountId}`]); + } + } + + applyFilter(filterValue: string = '') { + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + formatDate(date: any): string { + if (!date) return ''; + if (Array.isArray(date)) { + return new Date(date[0], date[1] - 1, date[2]).toLocaleDateString('en-IN', { + day: '2-digit', + month: 'short', + year: 'numeric' + }); + } + const d = new Date(date); + return isNaN(d.getTime()) + ? date + : d.toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); + } +} 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 349c6cfed..1b6c575b1 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 @@ -94,6 +94,18 @@ Pending Account + + + Pending Product Edits + Reject Account + + + Terminated Account + { + private productsService = inject(ProductsService); + + resolve(): Observable { + return forkJoin({ + savings: this.productsService.getPendingSavingsProductEdits().pipe(catchError(() => of([]))), + current: this.productsService.getPendingCurrentProductEdits().pipe(catchError(() => of([]))), + share: this.productsService.getPendingShareProductEdits().pipe(catchError(() => of([]))), + fd: this.productsService.getPendingFixedDepositProductEdits().pipe(catchError(() => of([]))), + rd: this.productsService.getPendingRecurringDepositProductEdits().pipe(catchError(() => of([]))) + }).pipe( + map((results) => [ + ...(results.savings || []).map((e: any) => ({ ...e, productType: 'SAVINGS' })), + ...(results.current || []).map((e: any) => ({ ...e, productType: 'CURRENT' })), + ...(results.share || []).map((e: any) => ({ ...e, productType: 'SHARE' })), + ...(results.fd || []).map((e: any) => ({ ...e, productType: 'FD' })), + ...(results.rd || []).map((e: any) => ({ ...e, productType: 'RD' })) + ]) + ); + } +} diff --git a/src/app/tasks/common-resolvers/getTerminatedAccounts.resolver.ts b/src/app/tasks/common-resolvers/getTerminatedAccounts.resolver.ts new file mode 100644 index 000000000..69191eaf9 --- /dev/null +++ b/src/app/tasks/common-resolvers/getTerminatedAccounts.resolver.ts @@ -0,0 +1,49 @@ +/** + * 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 { Injectable, inject } from '@angular/core'; +import { Resolve } from '@angular/router'; +import { Observable, forkJoin, of } from 'rxjs'; +import { map, catchError } from 'rxjs/operators'; +import { TasksService } from '../tasks.service'; + +@Injectable() +export class GetTerminatedAccounts implements Resolve { + private tasksService = inject(TasksService); + + resolve(): Observable { + return forkJoin({ + savings: this.tasksService.getTerminatedSavingsAccounts().pipe(catchError(() => of({ pageItems: [] }))), + current: this.tasksService.getTerminatedCurrentAccounts().pipe(catchError(() => of([]))), + offices: this.tasksService.getAllOffices().pipe(catchError(() => of([]))) + }).pipe( + map((results) => { + const officeMap: Record = {}; + (results.offices || []).forEach((o: any) => { + officeMap[o.id] = o.name; + }); + + const savingsItems = (results.savings?.pageItems || []).map((a: any) => ({ + ...a, + officeName: officeMap[a.officeId] || a.officeName || '', + fieldOfficerName: a.fieldOfficerName || '', + accountType: a.depositType?.value || 'Savings' + })); + + const currentItems = (Array.isArray(results.current) ? results.current : results.current?.pageItems || []).map( + (a: any) => ({ ...a, accountType: 'Current' }) + ); + + return { pageItems: [ + ...savingsItems, + ...currentItems + ] }; + }) + ); + } +} diff --git a/src/app/tasks/tasks-routing.module.ts b/src/app/tasks/tasks-routing.module.ts index 573faee07..273019314 100644 --- a/src/app/tasks/tasks-routing.module.ts +++ b/src/app/tasks/tasks-routing.module.ts @@ -25,6 +25,8 @@ import { LoanDisbursalComponent } from './checker-inbox-and-tasks-tabs/loan-disb import { RescheduleLoanComponent } from './checker-inbox-and-tasks-tabs/reschedule-loan/reschedule-loan.component'; import { PendingAccountComponent } from './checker-inbox-and-tasks-tabs/pending-account/pending-account.component'; import { RejectedAccountComponent } from './checker-inbox-and-tasks-tabs/rejected-account/rejected-account.component'; +import { TerminatedAccountComponent } from './checker-inbox-and-tasks-tabs/terminated-account/terminated-account.component'; +import { PendingSavingsProductsComponent } from './checker-inbox-and-tasks-tabs/pending-savings-products/pending-savings-products.component'; import { ViewCheckerInboxComponent } from './view-checker-inbox/view-checker-inbox.component'; /** Custom Resolvers */ @@ -38,6 +40,8 @@ import { MakerCheckerTemplate } from './common-resolvers/makerCheckerTemplate.re import { GetCheckerInboxDetailResolver } from './common-resolvers/getCheckerInboxDetail.resolver'; import { GetPendingAccounts } from './common-resolvers/getPendingAccounts.resolver'; import { GetRejectedAccounts } from './common-resolvers/getRejectedAccounts.resolver'; +import { GetTerminatedAccounts } from './common-resolvers/getTerminatedAccounts.resolver'; +import { GetPendingSavingsProducts } from './common-resolvers/getPendingSavingsProducts.resolver'; /** Tasks Routes */ const routes: Routes = [ @@ -105,6 +109,22 @@ const routes: Routes = [ rejectedAccountsData: GetRejectedAccounts } }, + { + path: 'terminated-account', + component: TerminatedAccountComponent, + data: { title: 'Terminated Account' }, + resolve: { + terminatedAccountsData: GetTerminatedAccounts + } + }, + { + path: 'pending-savings-products', + component: PendingSavingsProductsComponent, + data: { title: 'Pending Savings Product Edits' }, + resolve: { + pendingSavingsProductsData: GetPendingSavingsProducts + } + }, { path: 'withdrawal-requests', component: WithdrawalRequestsComponent, @@ -175,7 +195,9 @@ const routes: Routes = [ MakerCheckerTemplate, GetCheckerInboxDetailResolver, GetPendingAccounts, - GetRejectedAccounts + GetRejectedAccounts, + GetTerminatedAccounts, + GetPendingSavingsProducts ] }) export class TasksRoutingModule {} diff --git a/src/app/tasks/tasks.service.ts b/src/app/tasks/tasks.service.ts index 23587339a..294ce91de 100644 --- a/src/app/tasks/tasks.service.ts +++ b/src/app/tasks/tasks.service.ts @@ -140,6 +140,16 @@ export class TasksService { return this.http.get('/currentaccounts', { params: httpParams }); } + getTerminatedSavingsAccounts(): Observable { + const httpParams = new HttpParams().set('limit', '1000').set('status', 'terminated'); + return this.http.get('/savingsaccounts', { params: httpParams }); + } + + getTerminatedCurrentAccounts(): Observable { + const httpParams = new HttpParams().set('limit', '1000').set('status', 'terminated'); + return this.http.get('/currentaccounts', { params: httpParams }); + } + /** * Approve a savings account */ diff --git a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html index 0b38001f8..3ed785f19 100644 --- a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html +++ b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.html @@ -6,17 +6,26 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/. --> -
- - - -
+@if (isProductEditEntry) { +
+
+ This is a product edit awaiting approval. Approve or reject it from + Approvals > Pending Product Edits instead — actioning it here would not release its lock. +
+
+} @else { +
+ + + +
+}
diff --git a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.ts b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.ts index 8332c1d60..44ebee780 100644 --- a/src/app/tasks/view-checker-inbox/view-checker-inbox.component.ts +++ b/src/app/tasks/view-checker-inbox/view-checker-inbox.component.ts @@ -50,6 +50,24 @@ export class ViewCheckerInboxComponent { /** Checks if there is any object or not in jsondata */ displayJSONData = false; + /** + * These entities' pending edits are gated by our own m_savings_product_temp lock, not the native + * command-source pipeline. Approving/rejecting them here would replay the original command handler + * directly via /makercheckers/{id}?command=approve and never clear our temp table's lock. Action them + * from Approvals -> Pending Product Edits instead. + */ + private readonly productEditEntities = [ + 'SAVINGSPRODUCT', + 'CURRENTPRODUCT', + 'SHAREPRODUCT', + 'FIXEDDEPOSITPRODUCT', + 'RECURRINGDEPOSITPRODUCT' + ]; + + get isProductEditEntry(): boolean { + return this.productEditEntities.includes(this.checkerInboxDetail?.entityName); + } + /** * Retrieves the maker checker id data from `resolve`. * @param {ActivatedRoute} route Activated Route. diff --git a/src/assets/styles/_product-list.scss b/src/assets/styles/_product-list.scss index 4706b574b..d1cfc0934 100644 --- a/src/assets/styles/_product-list.scss +++ b/src/assets/styles/_product-list.scss @@ -540,6 +540,14 @@ border-color: var(--pl-amber); } } + + &--disabled, + &--disabled:hover { + background: #f1f5f9; + color: #94a3b8; + border-color: #e2e8f0; + cursor: not-allowed; + } } /* No-match strip */