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 <noreply@anthropic.com>
This commit is contained in:
-99
@@ -130,103 +130,4 @@
|
||||
<p class="empty-text">There are no accounts awaiting approval at this time.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Share accounts awaiting approval render here too, rather than on their own tab. Own table,
|
||||
own per-row approve/reject/view actions - unchanged from their previous standalone screen. -->
|
||||
<h3 class="pa-section-heading">Pending Share Accounts</h3>
|
||||
@if (showShareData) {
|
||||
<div class="psa-action-bar">
|
||||
<mat-form-field appearance="outline" class="psa-search-field">
|
||||
<mat-icon matPrefix>search</mat-icon>
|
||||
<input
|
||||
#shareFilterInput
|
||||
matInput
|
||||
placeholder="Search by account number, member or product"
|
||||
(keyup)="applyShareFilter(shareFilterInput.value)"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table mat-table [dataSource]="shareDataSource" class="modern-table">
|
||||
<ng-container matColumnDef="accountNo">
|
||||
<th mat-header-cell *matHeaderCellDef>Account No.</th>
|
||||
<td mat-cell *matCellDef="let account" class="psa-mono">{{ account.accountNo }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="clientName">
|
||||
<th mat-header-cell *matHeaderCellDef>Member</th>
|
||||
<td mat-cell *matCellDef="let account">
|
||||
{{ account.clientName }}
|
||||
@if (account.officeName) {
|
||||
<div class="psa-sub">{{ account.officeName }}</div>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="productName">
|
||||
<th mat-header-cell *matHeaderCellDef>Product</th>
|
||||
<td mat-cell *matCellDef="let account">{{ account.productName }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestedShares">
|
||||
<th mat-header-cell *matHeaderCellDef class="ta-right">Shares</th>
|
||||
<td mat-cell *matCellDef="let account" class="ta-right psa-num">{{ account.requestedShares }}</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="requestedAmount">
|
||||
<th mat-header-cell *matHeaderCellDef class="ta-right">Amount</th>
|
||||
<td mat-cell *matCellDef="let account" class="ta-right psa-num">
|
||||
{{ account.requestedAmount | number: '1.2-2' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="resolution">
|
||||
<th mat-header-cell *matHeaderCellDef>Resolution</th>
|
||||
<td mat-cell *matCellDef="let account">
|
||||
{{ account.resolutionNumber || '—' }}
|
||||
@if (account.resolutionDate) {
|
||||
<div class="psa-sub">{{ formatShareDate(account.resolutionDate) }}</div>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="submittedDate">
|
||||
<th mat-header-cell *matHeaderCellDef>Submitted</th>
|
||||
<td mat-cell *matCellDef="let account">
|
||||
{{ formatShareDate(account.submittedDate) }}
|
||||
@if (account.submittedByUsername) {
|
||||
<div class="psa-sub">by {{ account.submittedByUsername }}</div>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||
<td mat-cell *matCellDef="let account">
|
||||
<button mat-stroked-button class="psa-btn psa-btn-view" (click)="viewShareAccount(account)">
|
||||
<fa-icon icon="eye" class="m-r-10"></fa-icon>View
|
||||
</button>
|
||||
<button mat-stroked-button class="psa-btn psa-btn-approve" (click)="approveShareAccount(account)">
|
||||
<fa-icon icon="check" class="m-r-10"></fa-icon>Approve
|
||||
</button>
|
||||
<button mat-stroked-button class="psa-btn psa-btn-reject" (click)="rejectShareAccount(account)">
|
||||
<fa-icon icon="ban" class="m-r-10"></fa-icon>Reject
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="shareDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: shareDisplayedColumns" class="table-row"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!showShareData) {
|
||||
<div class="empty-state">
|
||||
<fa-icon icon="hourglass-half" class="empty-icon"></fa-icon>
|
||||
<p class="empty-title">No pending share accounts</p>
|
||||
<p class="empty-text">There are no share accounts awaiting approval at this time.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
-10
@@ -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;
|
||||
|
||||
+3
-120
@@ -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<any>;
|
||||
selection: SelectionModel<any>;
|
||||
@@ -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<any>;
|
||||
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
|
||||
|
||||
@@ -72,6 +72,15 @@
|
||||
<fa-icon icon="hourglass-half" class="ac-tab__ic"></fa-icon>
|
||||
<span>Pending Account</span>
|
||||
</a>
|
||||
<a
|
||||
class="ac-tab"
|
||||
routerLink="./pending-share-accounts"
|
||||
routerLinkActive="ac-tab--active"
|
||||
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||
>
|
||||
<fa-icon icon="hourglass-half" class="ac-tab__ic"></fa-icon>
|
||||
<span>Pending Share Accounts</span>
|
||||
</a>
|
||||
<a
|
||||
class="ac-tab"
|
||||
routerLink="./product-approval"
|
||||
|
||||
@@ -108,10 +108,7 @@ const routes: Routes = [
|
||||
component: PendingAccountComponent,
|
||||
data: { title: 'Pending Account' },
|
||||
resolve: {
|
||||
pendingAccountsData: GetPendingAccounts,
|
||||
// Share accounts awaiting approval now render inside this same screen instead of their
|
||||
// own tab - reuses the same resolver the standalone route below still uses.
|
||||
pendingShareAccountsData: GetPendingShareAccounts
|
||||
pendingAccountsData: GetPendingAccounts
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user