merge(release): merge feature/minor-update into release/share-account-module
Combines share-account-module UI changes with minor-update features. Resolved .env.ts conflict keeping newer version (260707/8a1b7d5d). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -89,4 +89,8 @@ export class AccountsService {
|
|||||||
if (params.asOnDate) httpParams = httpParams.set('asOnDate', params.asOnDate);
|
if (params.asOnDate) httpParams = httpParams.set('asOnDate', params.asOnDate);
|
||||||
return this.http.get('/recurringdepositaccounts', { params: httpParams });
|
return this.http.get('/recurringdepositaccounts', { params: httpParams });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
terminateAccount(accountId: string, data: any): Observable<any> {
|
||||||
|
return this.http.post(`/v1/accounts/${accountId}/terminate`, data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,14 +45,21 @@
|
|||||||
<input matInput type="date" [value]="settlementDate" (change)="settlementDate = $any($event.target).value" />
|
<input matInput type="date" [value]="settlementDate" (change)="settlementDate = $any($event.target).value" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
@if (tellers.length === 1) {
|
||||||
<mat-label>Teller</mat-label>
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-select [value]="supervisorTellerId" (selectionChange)="onSupervisorTellerSelect($event.value)">
|
<mat-label>Teller</mat-label>
|
||||||
@for (t of tellers; track t.id) {
|
<input matInput type="text" [value]="tellers[0].name" readonly />
|
||||||
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
</mat-form-field>
|
||||||
}
|
} @else {
|
||||||
</mat-select>
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
</mat-form-field>
|
<mat-label>Teller</mat-label>
|
||||||
|
<mat-select [value]="supervisorTellerId" (selectionChange)="onSupervisorTellerSelect($event.value)">
|
||||||
|
@for (t of tellers; track t.id) {
|
||||||
|
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-label>Cashier</mat-label>
|
<mat-label>Cashier</mat-label>
|
||||||
@@ -140,17 +147,24 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
@if (tellers.length === 1) {
|
||||||
<mat-label>Teller / Vault (To) *</mat-label>
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-select [value]="selectedTellerId" (selectionChange)="onTellerSelect($event.value)">
|
<mat-label>Teller / Vault (To) *</mat-label>
|
||||||
@for (t of tellers; track t.id) {
|
<input matInput type="text" [value]="toParty" readonly />
|
||||||
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
</mat-form-field>
|
||||||
}
|
} @else {
|
||||||
@if (tellers.length === 0) {
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-option [value]="null" disabled>No tellers configured</mat-option>
|
<mat-label>Teller / Vault (To) *</mat-label>
|
||||||
}
|
<mat-select [value]="selectedTellerId" (selectionChange)="onTellerSelect($event.value)">
|
||||||
</mat-select>
|
@for (t of tellers; track t.id) {
|
||||||
</mat-form-field>
|
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
||||||
|
}
|
||||||
|
@if (tellers.length === 0) {
|
||||||
|
<mat-option [value]="null" disabled>No tellers configured</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-label>Settlement Date</mat-label>
|
<mat-label>Settlement Date</mat-label>
|
||||||
|
|||||||
@@ -97,6 +97,10 @@ export class CashSettlementComponent implements OnInit {
|
|||||||
.pipe(catchError(() => of([] as any[])))
|
.pipe(catchError(() => of([] as any[])))
|
||||||
.subscribe((list: any[]) => {
|
.subscribe((list: any[]) => {
|
||||||
this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name }));
|
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();
|
this.loadData();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,17 +17,24 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="pg-toolbar pg-toolbar--mat">
|
<div class="pg-toolbar pg-toolbar--mat">
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
@if (tellers.length === 1) {
|
||||||
<mat-label>From (Teller / Head)</mat-label>
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-select [value]="fromTellerId" (selectionChange)="onTellerSelect($event.value)">
|
<mat-label>From (Teller / Head)</mat-label>
|
||||||
@for (t of tellers; track t.id) {
|
<input matInput type="text" [value]="fromParty" readonly />
|
||||||
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
</mat-form-field>
|
||||||
}
|
} @else {
|
||||||
@if (tellers.length === 0) {
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-option [value]="null" disabled>No tellers configured</mat-option>
|
<mat-label>From (Teller / Head)</mat-label>
|
||||||
}
|
<mat-select [value]="fromTellerId" (selectionChange)="onTellerSelect($event.value)">
|
||||||
</mat-select>
|
@for (t of tellers; track t.id) {
|
||||||
</mat-form-field>
|
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
||||||
|
}
|
||||||
|
@if (tellers.length === 0) {
|
||||||
|
<mat-option [value]="null" disabled>No tellers configured</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-label>To (Cashier)</mat-label>
|
<mat-label>To (Cashier)</mat-label>
|
||||||
|
|||||||
@@ -95,6 +95,9 @@ export class CashTransferComponent implements OnInit {
|
|||||||
.pipe(catchError(() => of([] as any[])))
|
.pipe(catchError(() => of([] as any[])))
|
||||||
.subscribe((list) => {
|
.subscribe((list) => {
|
||||||
this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name }));
|
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) => {
|
.subscribe((r: any) => {
|
||||||
this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName }));
|
this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName }));
|
||||||
this.loadingCashiers = false;
|
this.loadingCashiers = false;
|
||||||
|
if (this.cashiers.length === 1) {
|
||||||
|
this.onCashierSelect(this.cashiers[0].id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,22 @@
|
|||||||
<div class="pg-toolbar pg-toolbar--mat">
|
<div class="pg-toolbar pg-toolbar--mat">
|
||||||
<!-- Teller + Cashier filters — hidden for cashier role (auto-loads own position) -->
|
<!-- Teller + Cashier filters — hidden for cashier role (auto-loads own position) -->
|
||||||
@if (!isCashierMode) {
|
@if (!isCashierMode) {
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
@if (tellers.length === 1) {
|
||||||
<mat-label>Teller (optional)</mat-label>
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-select [value]="selectedTellerId" (selectionChange)="onTellerSelect($event.value)">
|
<mat-label>Teller</mat-label>
|
||||||
<mat-option [value]="null">All Tellers</mat-option>
|
<input matInput type="text" [value]="tellers[0].name" readonly />
|
||||||
@for (t of tellers; track t.id) {
|
</mat-form-field>
|
||||||
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
} @else {
|
||||||
}
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
</mat-select>
|
<mat-label>Teller (optional)</mat-label>
|
||||||
</mat-form-field>
|
<mat-select [value]="selectedTellerId" (selectionChange)="onTellerSelect($event.value)">
|
||||||
|
<mat-option [value]="null">All Tellers</mat-option>
|
||||||
|
@for (t of tellers; track t.id) {
|
||||||
|
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-label>Cashier (optional)</mat-label>
|
<mat-label>Cashier (optional)</mat-label>
|
||||||
|
|||||||
@@ -85,6 +85,9 @@ export class CashierPositionComponent implements OnInit {
|
|||||||
.pipe(catchError(() => of([] as any[])))
|
.pipe(catchError(() => of([] as any[])))
|
||||||
.subscribe((list) => {
|
.subscribe((list) => {
|
||||||
this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name }));
|
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: [] })))
|
.pipe(catchError(() => of({ cashiers: [] })))
|
||||||
.subscribe((r: any) => {
|
.subscribe((r: any) => {
|
||||||
this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName }));
|
this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName }));
|
||||||
|
if (this.cashiers.length === 1) {
|
||||||
|
this.selectedCashierId = this.cashiers[0].id;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,24 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="pg-toolbar pg-toolbar--mat">
|
<div class="pg-toolbar pg-toolbar--mat">
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
@if (tellers.length === 1) {
|
||||||
<mat-label>Teller</mat-label>
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-select [value]="selectedTellerId" (selectionChange)="onTellerSelect($event.value)">
|
<mat-label>Teller</mat-label>
|
||||||
@for (t of tellers; track t.id) {
|
<input matInput type="text" [value]="tellers[0].name" readonly />
|
||||||
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
</mat-form-field>
|
||||||
}
|
} @else {
|
||||||
@if (tellers.length === 0) {
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-option [value]="null" disabled>No tellers configured</mat-option>
|
<mat-label>Teller</mat-label>
|
||||||
}
|
<mat-select [value]="selectedTellerId" (selectionChange)="onTellerSelect($event.value)">
|
||||||
</mat-select>
|
@for (t of tellers; track t.id) {
|
||||||
</mat-form-field>
|
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
||||||
|
}
|
||||||
|
@if (tellers.length === 0) {
|
||||||
|
<mat-option [value]="null" disabled>No tellers configured</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-label>From (Cashier)</mat-label>
|
<mat-label>From (Cashier)</mat-label>
|
||||||
|
|||||||
@@ -102,6 +102,9 @@ export class InterCashierTransferComponent implements OnInit {
|
|||||||
.pipe(catchError(() => of([] as any[])))
|
.pipe(catchError(() => of([] as any[])))
|
||||||
.subscribe((list) => {
|
.subscribe((list) => {
|
||||||
this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name }));
|
this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name }));
|
||||||
|
if (this.tellers.length === 1) {
|
||||||
|
this.onTellerSelect(this.tellers[0].id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,21 @@
|
|||||||
<mat-tab label="New Reconciliation">
|
<mat-tab label="New Reconciliation">
|
||||||
<div class="pg-tab-body">
|
<div class="pg-tab-body">
|
||||||
<div class="pg-toolbar pg-toolbar--mat">
|
<div class="pg-toolbar pg-toolbar--mat">
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
@if (tellers.length === 1) {
|
||||||
<mat-label>Teller</mat-label>
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-select [value]="selectedTellerId" (selectionChange)="onTellerSelect($event.value)">
|
<mat-label>Teller</mat-label>
|
||||||
@for (t of tellers; track t.id) {
|
<input matInput type="text" [value]="tellers[0].name" readonly />
|
||||||
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
</mat-form-field>
|
||||||
}
|
} @else {
|
||||||
</mat-select>
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
</mat-form-field>
|
<mat-label>Teller</mat-label>
|
||||||
|
<mat-select [value]="selectedTellerId" (selectionChange)="onTellerSelect($event.value)">
|
||||||
|
@for (t of tellers; track t.id) {
|
||||||
|
<mat-option [value]="t.id">{{ t.name }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
||||||
<mat-label>Cashier</mat-label>
|
<mat-label>Cashier</mat-label>
|
||||||
|
|||||||
@@ -92,6 +92,9 @@ export class ReconciliationComponent implements OnInit {
|
|||||||
.pipe(catchError(() => of([] as any[])))
|
.pipe(catchError(() => of([] as any[])))
|
||||||
.subscribe((list) => {
|
.subscribe((list) => {
|
||||||
this.tellers = (list || []).map((t: any) => ({ id: t.id, name: t.name }));
|
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();
|
this.loadHistory();
|
||||||
}
|
}
|
||||||
@@ -108,6 +111,9 @@ export class ReconciliationComponent implements OnInit {
|
|||||||
.pipe(catchError(() => of({ cashiers: [] })))
|
.pipe(catchError(() => of({ cashiers: [] })))
|
||||||
.subscribe((r: any) => {
|
.subscribe((r: any) => {
|
||||||
this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName }));
|
this.cashiers = (r?.cashiers || []).map((c: any) => ({ id: c.id, label: c.staffName }));
|
||||||
|
if (this.cashiers.length === 1) {
|
||||||
|
this.onCashierSelect(this.cashiers[0].id);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -168,13 +168,6 @@
|
|||||||
>
|
>
|
||||||
<fa-icon icon="money-bill-wave"></fa-icon> My Deposits
|
<fa-icon icon="money-bill-wave"></fa-icon> My Deposits
|
||||||
</a>
|
</a>
|
||||||
<a
|
|
||||||
class="cashier-nav__item"
|
|
||||||
[routerLink]="['/cashier/share-collection']"
|
|
||||||
routerLinkActive="cashier-nav__item--active"
|
|
||||||
>
|
|
||||||
<fa-icon icon="hand-holding-usd"></fa-icon> Share Collection
|
|
||||||
</a>
|
|
||||||
<a
|
<a
|
||||||
class="cashier-nav__item"
|
class="cashier-nav__item"
|
||||||
[routerLink]="['/cashier/cash-exchange']"
|
[routerLink]="['/cashier/cash-exchange']"
|
||||||
@@ -915,9 +908,6 @@
|
|||||||
<button mat-menu-item tabindex="0" [routerLink]="['/cashier/cash-history']">
|
<button mat-menu-item tabindex="0" [routerLink]="['/cashier/cash-history']">
|
||||||
<fa-icon icon="history" class="menu-item-icon"></fa-icon> Cash History
|
<fa-icon icon="history" class="menu-item-icon"></fa-icon> Cash History
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item tabindex="0" [routerLink]="['/cashier/share-collection']">
|
|
||||||
<fa-icon icon="hand-holding-usd" class="menu-item-icon"></fa-icon> Share Collection
|
|
||||||
</button>
|
|
||||||
<button mat-menu-item tabindex="0" [routerLink]="['/cashier/transaction-entry']">
|
<button mat-menu-item tabindex="0" [routerLink]="['/cashier/transaction-entry']">
|
||||||
<fa-icon icon="keyboard" class="menu-item-icon"></fa-icon> Transaction Entry
|
<fa-icon icon="keyboard" class="menu-item-icon"></fa-icon> Transaction Entry
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+7
-6
@@ -155,12 +155,13 @@
|
|||||||
<input matInput [value]="productTemplate.introducerRequired ? 'Yes' : 'No'" readonly />
|
<input matInput [value]="productTemplate.introducerRequired ? 'Yes' : 'No'" readonly />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="sad-field sad-info-row">
|
<mat-form-field appearance="outline" class="sad-field">
|
||||||
<span class="sad-info-label">Share Linking Enabled</span>
|
<mat-label>Share Linking Enabled</mat-label>
|
||||||
<span class="sad-badge" [class.sad-badge--yes]="productTemplate.shareLinkingEnabled">
|
<mat-select [(ngModel)]="shareLinkingEnabled" [ngModelOptions]="{ standalone: true }">
|
||||||
{{ productTemplate.shareLinkingEnabled ? 'Yes' : 'No' }}
|
<mat-option [value]="true">Yes</mat-option>
|
||||||
</span>
|
<mat-option [value]="false">No</mat-option>
|
||||||
</div>
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+5
@@ -67,6 +67,8 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
introducerCif: string | null = null;
|
introducerCif: string | null = null;
|
||||||
private isEditMode = false;
|
private isEditMode = false;
|
||||||
|
|
||||||
|
shareLinkingEnabled = false;
|
||||||
|
|
||||||
// ── Non-Individual (existing) ──────────────────────────────────────────
|
// ── Non-Individual (existing) ──────────────────────────────────────────
|
||||||
isNonIndividualClient = false;
|
isNonIndividualClient = false;
|
||||||
relatedClients: Array<{
|
relatedClients: Array<{
|
||||||
@@ -280,6 +282,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
this.currentAccountProductTemplate.emit(response);
|
this.currentAccountProductTemplate.emit(response);
|
||||||
this.fieldOfficerData = response.fieldOfficerOptions;
|
this.fieldOfficerData = response.fieldOfficerOptions;
|
||||||
this.currentAccountProductSelected = true;
|
this.currentAccountProductSelected = true;
|
||||||
|
this.shareLinkingEnabled = !!response.shareLinkingEnabled;
|
||||||
this.currentAccountDetailsForm.patchValue({
|
this.currentAccountDetailsForm.patchValue({
|
||||||
balanceSwapOption: !!response.balanceSwapOption,
|
balanceSwapOption: !!response.balanceSwapOption,
|
||||||
msme: !!response.msme,
|
msme: !!response.msme,
|
||||||
@@ -382,6 +385,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
this.introducerName = ea.introducerName ?? null;
|
this.introducerName = ea.introducerName ?? null;
|
||||||
this.introducerCif = ea.introducerCifNumber ?? null;
|
this.introducerCif = ea.introducerCifNumber ?? null;
|
||||||
}
|
}
|
||||||
|
this.shareLinkingEnabled = ea.shareLinkingEnabled !== undefined ? !!ea.shareLinkingEnabled : !!ea.isLinkingEnable;
|
||||||
this.currentAccountDetailsForm.patchValue({
|
this.currentAccountDetailsForm.patchValue({
|
||||||
balanceSwapOption: !!ea.balanceSwapOption,
|
balanceSwapOption: !!ea.balanceSwapOption,
|
||||||
msme: !!ea.msme,
|
msme: !!ea.msme,
|
||||||
@@ -664,6 +668,7 @@ export class CurrentAccountDetailsStepComponent implements OnInit {
|
|||||||
|
|
||||||
get currentAccountDetails() {
|
get currentAccountDetails() {
|
||||||
const value = this.currentAccountDetailsForm.getRawValue();
|
const value = this.currentAccountDetailsForm.getRawValue();
|
||||||
|
value.shareLinkingEnabled = this.shareLinkingEnabled;
|
||||||
if (this.introducerId) {
|
if (this.introducerId) {
|
||||||
value.introducerId = this.introducerId;
|
value.introducerId = this.introducerId;
|
||||||
value.introducerName = this.introducerName;
|
value.introducerName = this.introducerName;
|
||||||
|
|||||||
+11
-2
@@ -131,9 +131,18 @@
|
|||||||
<span class="preview-label">Share Linking Enabled</span>
|
<span class="preview-label">Share Linking Enabled</span>
|
||||||
<span
|
<span
|
||||||
class="preview-value preview-badge"
|
class="preview-value preview-badge"
|
||||||
[class.preview-badge--yes]="currentAccountProductTemplate?.shareLinkingEnabled"
|
[class.preview-badge--yes]="
|
||||||
|
currentAccount?.shareLinkingEnabled !== undefined
|
||||||
|
? currentAccount.shareLinkingEnabled
|
||||||
|
: currentAccountProductTemplate?.shareLinkingEnabled
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ currentAccountProductTemplate?.shareLinkingEnabled | yesNo }}
|
{{
|
||||||
|
(currentAccount?.shareLinkingEnabled !== undefined
|
||||||
|
? currentAccount.shareLinkingEnabled
|
||||||
|
: currentAccountProductTemplate?.shareLinkingEnabled
|
||||||
|
) | yesNo
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import { ClientsService } from 'app/clients/clients.service';
|
|||||||
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { Currency } from 'app/shared/models/general.model';
|
import { Currency } from 'app/shared/models/general.model';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
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 */
|
/** Environment Configuration */
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
@@ -83,6 +86,9 @@ export class CurrentAccountViewComponent implements OnInit {
|
|||||||
private currentAccountsService = inject(CurrentAccountsService);
|
private currentAccountsService = inject(CurrentAccountsService);
|
||||||
private clientsService = inject(ClientsService);
|
private clientsService = inject(ClientsService);
|
||||||
private translateService = inject(TranslateService);
|
private translateService = inject(TranslateService);
|
||||||
|
private accountsService = inject(AccountsService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
dialog = inject(MatDialog);
|
dialog = inject(MatDialog);
|
||||||
|
|
||||||
/** Current Account Data */
|
/** Current Account Data */
|
||||||
@@ -126,7 +132,8 @@ export class CurrentAccountViewComponent implements OnInit {
|
|||||||
Close: 'danger',
|
Close: 'danger',
|
||||||
Reject: 'danger',
|
Reject: 'danger',
|
||||||
Delete: 'danger',
|
Delete: 'danger',
|
||||||
'Withdrawn by Client': 'danger'
|
'Withdrawn by Client': 'danger',
|
||||||
|
Terminate: 'danger'
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly ACTION_ICON: Record<string, string> = {
|
private readonly ACTION_ICON: Record<string, string> = {
|
||||||
@@ -157,7 +164,8 @@ export class CurrentAccountViewComponent implements OnInit {
|
|||||||
Close: 'times-circle',
|
Close: 'times-circle',
|
||||||
Reject: 'ban',
|
Reject: 'ban',
|
||||||
Delete: 'trash',
|
Delete: 'trash',
|
||||||
'Withdrawn by Client': 'user-slash'
|
'Withdrawn by Client': 'user-slash',
|
||||||
|
Terminate: 'times-circle'
|
||||||
};
|
};
|
||||||
|
|
||||||
kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' {
|
kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' {
|
||||||
@@ -309,6 +317,9 @@ export class CurrentAccountViewComponent implements OnInit {
|
|||||||
case 'Delete':
|
case 'Delete':
|
||||||
this.deleteCurrentAccount();
|
this.deleteCurrentAccount();
|
||||||
break;
|
break;
|
||||||
|
case 'Terminate':
|
||||||
|
this.terminateCurrentAccount();
|
||||||
|
break;
|
||||||
case 'Calculate Interest':
|
case 'Calculate Interest':
|
||||||
this.calculateInterest();
|
this.calculateInterest();
|
||||||
break;
|
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
|
* Calculates current account's interest
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -194,6 +194,10 @@ export class CurrentAccountButtonsConfiguration {
|
|||||||
{
|
{
|
||||||
name: 'Delete',
|
name: 'Delete',
|
||||||
taskPermissionName: 'DELETE_CURRENTACCOUNT'
|
taskPermissionName: 'DELETE_CURRENTACCOUNT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Terminate',
|
||||||
|
taskPermissionName: 'DELETE_CURRENTACCOUNT'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ export class EditFixedDepositAccountComponent {
|
|||||||
if (account?.depositPeriod) {
|
if (account?.depositPeriod) {
|
||||||
this.selectedTenure = {
|
this.selectedTenure = {
|
||||||
depositPeriod: account.depositPeriod,
|
depositPeriod: account.depositPeriod,
|
||||||
depositPeriodFrequencyId: account.depositPeriodFrequencyType?.id ?? 2,
|
depositPeriodFrequencyId: account.depositPeriodFrequency?.id ?? 2,
|
||||||
depositAmount: account.depositAmount,
|
depositAmount: account.depositAmount,
|
||||||
isSeniorCitizen: !!account.isSeniorCitizen,
|
isSeniorCitizen: !!account.isSeniorCitizen,
|
||||||
appliedRate: account.seniorCitizenInterestRate ?? undefined
|
appliedRate: account.seniorCitizenInterestRate ?? undefined
|
||||||
|
|||||||
+3
@@ -50,6 +50,9 @@ export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
if (Array.isArray(ea.nominees) && ea.nominees.length > 0) {
|
if (Array.isArray(ea.nominees) && ea.nominees.length > 0) {
|
||||||
this.nominees = [...ea.nominees];
|
this.nominees = [...ea.nominees];
|
||||||
|
this.nominationFacility = true;
|
||||||
|
} else {
|
||||||
|
this.nominationFacility = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (changes['fixedDepositsAccountProductTemplate']?.currentValue && !this.existingAccountApplied) {
|
if (changes['fixedDepositsAccountProductTemplate']?.currentValue && !this.existingAccountApplied) {
|
||||||
|
|||||||
+40
-3
@@ -11,8 +11,8 @@ import { Component, OnInit, inject } from '@angular/core';
|
|||||||
import { ActivatedRoute, Router, RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
|
||||||
/** Custom Dialogs */
|
|
||||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
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 { CalculateInterestDialogComponent } from './custom-dialogs/calculate-interest-dialog/calculate-interest-dialog.component';
|
||||||
import { PostInterestDialogComponent } from './custom-dialogs/post-interest-dialog/post-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';
|
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 */
|
/** Custom Services */
|
||||||
import { FixedDepositsService } from '../fixed-deposits.service';
|
import { FixedDepositsService } from '../fixed-deposits.service';
|
||||||
import { SavingsService } from 'app/savings/savings.service';
|
import { SavingsService } from 'app/savings/savings.service';
|
||||||
|
import { AccountsService } from 'app/accounts/accounts.service';
|
||||||
import { Currency } from 'app/shared/models/general.model';
|
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 { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { NgClass, CurrencyPipe } from '@angular/common';
|
import { NgClass, CurrencyPipe } from '@angular/common';
|
||||||
import { AccountNumberComponent } from '../../../shared/account-number/account-number.component';
|
import { AccountNumberComponent } from '../../../shared/account-number/account-number.component';
|
||||||
@@ -67,6 +70,9 @@ export class FixedDepositAccountViewComponent implements OnInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private fixedDepositsService = inject(FixedDepositsService);
|
private fixedDepositsService = inject(FixedDepositsService);
|
||||||
private savingsService = inject(SavingsService);
|
private savingsService = inject(SavingsService);
|
||||||
|
private accountsService = inject(AccountsService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
dialog = inject(MatDialog);
|
dialog = inject(MatDialog);
|
||||||
|
|
||||||
/** Fixed Deposits Account Data */
|
/** Fixed Deposits Account Data */
|
||||||
@@ -99,7 +105,8 @@ export class FixedDepositAccountViewComponent implements OnInit {
|
|||||||
'Apply Annual Fees': 'admin',
|
'Apply Annual Fees': 'admin',
|
||||||
Reject: 'danger',
|
Reject: 'danger',
|
||||||
'Withdrawn by Client': 'danger',
|
'Withdrawn by Client': 'danger',
|
||||||
Delete: 'danger'
|
Delete: 'danger',
|
||||||
|
Terminate: 'danger'
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly ACTION_ICON: Record<string, string> = {
|
private readonly ACTION_ICON: Record<string, string> = {
|
||||||
@@ -121,7 +128,8 @@ export class FixedDepositAccountViewComponent implements OnInit {
|
|||||||
'Apply Annual Fees': 'receipt',
|
'Apply Annual Fees': 'receipt',
|
||||||
Reject: 'ban',
|
Reject: 'ban',
|
||||||
'Withdrawn by Client': 'user-slash',
|
'Withdrawn by Client': 'user-slash',
|
||||||
Delete: 'trash'
|
Delete: 'trash',
|
||||||
|
Terminate: 'times-circle'
|
||||||
};
|
};
|
||||||
|
|
||||||
kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' {
|
kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' {
|
||||||
@@ -237,6 +245,9 @@ export class FixedDepositAccountViewComponent implements OnInit {
|
|||||||
case 'Disable Withhold Tax':
|
case 'Disable Withhold Tax':
|
||||||
this.disableWithHoldTax();
|
this.disableWithHoldTax();
|
||||||
break;
|
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
|
* Calculates fixed deposit account's interest
|
||||||
*/
|
*/
|
||||||
|
|||||||
+3
@@ -132,6 +132,9 @@ export class FixedDepositsButtonsConfiguration {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Delete'
|
name: 'Delete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Terminate'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|||||||
+1
-1
@@ -86,7 +86,7 @@ export class EditRecurringDepositAccountComponent {
|
|||||||
// Pre-populate tenure from existing account
|
// Pre-populate tenure from existing account
|
||||||
if (account?.depositPeriod) {
|
if (account?.depositPeriod) {
|
||||||
this.selectedTenureValue = account.depositPeriod;
|
this.selectedTenureValue = account.depositPeriod;
|
||||||
this.selectedTenureFrequencyId = account.depositPeriodFrequencyType?.id ?? 2;
|
this.selectedTenureFrequencyId = account.depositPeriodFrequency?.id ?? 2;
|
||||||
}
|
}
|
||||||
if (account?.mandatoryRecommendedDepositAmount) {
|
if (account?.mandatoryRecommendedDepositAmount) {
|
||||||
this.selectedDepositAmount = account.mandatoryRecommendedDepositAmount;
|
this.selectedDepositAmount = account.mandatoryRecommendedDepositAmount;
|
||||||
|
|||||||
+40
-2
@@ -20,9 +20,13 @@ import { RecurringDepositsButtonsConfiguration } from './recurring-deposits-butt
|
|||||||
|
|
||||||
/** Custom Dialogs */
|
/** Custom Dialogs */
|
||||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
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 { RecurringDepositConfirmationDialogComponent } from './custom-dialogs/recurring-deposit-confirmation-dialog/recurring-deposit-confirmation-dialog.component';
|
||||||
import { Currency } from 'app/shared/models/general.model';
|
import { Currency } from 'app/shared/models/general.model';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
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 { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { NgClass, DecimalPipe, CurrencyPipe } from '@angular/common';
|
import { NgClass, DecimalPipe, CurrencyPipe } from '@angular/common';
|
||||||
import { AccountNumberComponent } from '../../../shared/account-number/account-number.component';
|
import { AccountNumberComponent } from '../../../shared/account-number/account-number.component';
|
||||||
@@ -67,6 +71,9 @@ export class RecurringDepositsAccountViewComponent implements OnInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private recurringDepositsService = inject(RecurringDepositsService);
|
private recurringDepositsService = inject(RecurringDepositsService);
|
||||||
private savingsService = inject(SavingsService);
|
private savingsService = inject(SavingsService);
|
||||||
|
private accountsService = inject(AccountsService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
dialog = inject(MatDialog);
|
dialog = inject(MatDialog);
|
||||||
private translateService = inject(TranslateService);
|
private translateService = inject(TranslateService);
|
||||||
|
|
||||||
@@ -103,7 +110,8 @@ export class RecurringDepositsAccountViewComponent implements OnInit {
|
|||||||
'Apply Annual Fees': 'admin',
|
'Apply Annual Fees': 'admin',
|
||||||
Reject: 'danger',
|
Reject: 'danger',
|
||||||
'Withdrawn by Client': 'danger',
|
'Withdrawn by Client': 'danger',
|
||||||
Delete: 'danger'
|
Delete: 'danger',
|
||||||
|
Terminate: 'danger'
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly ACTION_ICON: Record<string, string> = {
|
private readonly ACTION_ICON: Record<string, string> = {
|
||||||
@@ -125,7 +133,8 @@ export class RecurringDepositsAccountViewComponent implements OnInit {
|
|||||||
'Apply Annual Fees': 'receipt',
|
'Apply Annual Fees': 'receipt',
|
||||||
Reject: 'ban',
|
Reject: 'ban',
|
||||||
'Withdrawn by Client': 'user-slash',
|
'Withdrawn by Client': 'user-slash',
|
||||||
Delete: 'trash'
|
Delete: 'trash',
|
||||||
|
Terminate: 'times-circle'
|
||||||
};
|
};
|
||||||
|
|
||||||
kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' {
|
kindOf(name: string): 'transact' | 'block' | 'interest' | 'admin' | 'danger' {
|
||||||
@@ -287,6 +296,9 @@ export class RecurringDepositsAccountViewComponent implements OnInit {
|
|||||||
case 'Disable Withhold Tax':
|
case 'Disable Withhold Tax':
|
||||||
this.disableWithHoldTax();
|
this.disableWithHoldTax();
|
||||||
break;
|
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
|
* Calculates savings account's interest
|
||||||
*/
|
*/
|
||||||
|
|||||||
+5
@@ -163,6 +163,11 @@ export class RecurringDepositsButtonsConfiguration {
|
|||||||
name: 'Delete',
|
name: 'Delete',
|
||||||
icon: 'trash',
|
icon: 'trash',
|
||||||
taskPermissionName: 'DELETE_RECURRINGDEPOSITACCOUNT'
|
taskPermissionName: 'DELETE_RECURRINGDEPOSITACCOUNT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Terminate',
|
||||||
|
icon: 'times-circle',
|
||||||
|
taskPermissionName: 'DELETE_RECURRINGDEPOSITACCOUNT'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -54,38 +54,6 @@
|
|||||||
<td mat-cell *matCellDef="let cashier">{{ cashier.isFullDay | yesNo }}</td>
|
<td mat-cell *matCellDef="let cashier">{{ cashier.isFullDay | yesNo }}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="vaultActions">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Vault Actions' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let cashier" (click)="$event.stopPropagation()">
|
|
||||||
<div class="vault-actions">
|
|
||||||
<a
|
|
||||||
class="vault-btn vault-btn--allocate"
|
|
||||||
matTooltip="{{ 'tooltips.Allocate Cash' | translate }}"
|
|
||||||
[routerLink]="[cashier.id, 'allocate']"
|
|
||||||
*mifosxHasPermission="'ALLOCATECASHIER_TELLER'"
|
|
||||||
>
|
|
||||||
<i class="fas fa-arrow-up"></i>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="vault-btn vault-btn--settle"
|
|
||||||
matTooltip="{{ 'tooltips.Settle Cash' | translate }}"
|
|
||||||
[routerLink]="[cashier.id, 'settle']"
|
|
||||||
*mifosxHasPermission="'SETTLECASHIER_TELLER'"
|
|
||||||
>
|
|
||||||
<i class="fas fa-arrow-down"></i>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
class="vault-btn vault-btn--view"
|
|
||||||
matTooltip="{{ 'tooltips.View Transactions' | translate }}"
|
|
||||||
[routerLink]="[cashier.id, 'transactions']"
|
|
||||||
*mifosxHasPermission="'ALLOCATECASHIER_TELLER'"
|
|
||||||
>
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row" [routerLink]="[row.id]"></tr>
|
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row" [routerLink]="[row.id]"></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -67,8 +67,7 @@ export class CashiersComponent implements OnInit {
|
|||||||
displayedColumns: string[] = [
|
displayedColumns: string[] = [
|
||||||
'period',
|
'period',
|
||||||
'staffName',
|
'staffName',
|
||||||
'isFullDay',
|
'isFullDay'
|
||||||
'vaultActions'
|
|
||||||
];
|
];
|
||||||
/** Data source for cashiers table. */
|
/** Data source for cashiers table. */
|
||||||
dataSource: MatTableDataSource<any>;
|
dataSource: MatTableDataSource<any>;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Filter' | translate }}</mat-label>
|
||||||
<input matInput (keyup)="applyFilter($event.target.value)" />
|
<input matInput (keyup)="applyFilter($event.target.value)" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<div class="table-buttons">
|
<div class="table-buttons" *ngIf="!tellersData || tellersData.length === 0">
|
||||||
<button
|
<button
|
||||||
mat-flat-button
|
mat-flat-button
|
||||||
color="primary"
|
color="primary"
|
||||||
@@ -78,7 +78,5 @@
|
|||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
|
<tr mat-row *matRowDef="let row; columns: displayedColumns" [routerLink]="[row.id]" class="table-row"></tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<mat-paginator [pageSizeOptions]="[10, 25, 50, 100]" showFirstLastButtons></mat-paginator>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
/** Angular Imports */
|
/** Angular Imports */
|
||||||
import { Component, OnInit, ViewChild, inject } from '@angular/core';
|
import { Component, OnInit, ViewChild, inject } from '@angular/core';
|
||||||
import { MatPaginator } from '@angular/material/paginator';
|
|
||||||
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
import { MatSort, MatSortHeader } from '@angular/material/sort';
|
||||||
import {
|
import {
|
||||||
MatTableDataSource,
|
MatTableDataSource,
|
||||||
@@ -55,7 +54,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
MatHeaderRow,
|
MatHeaderRow,
|
||||||
MatRowDef,
|
MatRowDef,
|
||||||
MatRow,
|
MatRow,
|
||||||
MatPaginator,
|
|
||||||
DateFormatPipe
|
DateFormatPipe
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -75,8 +73,6 @@ export class TellersComponent implements OnInit {
|
|||||||
/** Data source for tellers table. */
|
/** Data source for tellers table. */
|
||||||
dataSource: MatTableDataSource<any>;
|
dataSource: MatTableDataSource<any>;
|
||||||
|
|
||||||
/** Paginator for tellers table. */
|
|
||||||
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
|
|
||||||
/** Sorter for tellers table. */
|
/** Sorter for tellers table. */
|
||||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
||||||
|
|
||||||
@@ -110,7 +106,6 @@ export class TellersComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
setTellers() {
|
setTellers() {
|
||||||
this.dataSource = new MatTableDataSource(this.tellersData);
|
this.dataSource = new MatTableDataSource(this.tellersData);
|
||||||
this.dataSource.paginator = this.paginator;
|
|
||||||
this.dataSource.sort = this.sort;
|
this.dataSource.sort = this.sort;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export class StatusLookupPipe implements PipeTransform {
|
|||||||
'savingsAccountStatusType.activeInactive': 'status-active-overdue',
|
'savingsAccountStatusType.activeInactive': 'status-active-overdue',
|
||||||
'savingsAccountStatusType.activeDormant': 'status-active-overdue',
|
'savingsAccountStatusType.activeDormant': 'status-active-overdue',
|
||||||
'savingsAccountStatusType.matured': 'status-matured',
|
'savingsAccountStatusType.matured': 'status-matured',
|
||||||
|
'savingsAccountStatusType.terminated': 'status-deleted',
|
||||||
'shareAccountStatusType.submitted.and.pending.approval': 'status-pending',
|
'shareAccountStatusType.submitted.and.pending.approval': 'status-pending',
|
||||||
'shareAccountStatusType.approved': 'status-approved',
|
'shareAccountStatusType.approved': 'status-approved',
|
||||||
'shareAccountStatusType.active': 'status-active',
|
'shareAccountStatusType.active': 'status-active',
|
||||||
|
|||||||
@@ -135,12 +135,19 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="pl-action-btn pl-action-btn--edit"
|
class="pl-action-btn pl-action-btn--edit"
|
||||||
[routerLink]="[p.id, 'edit']"
|
[class.pl-action-btn--disabled]="p.isPendingApproval"
|
||||||
|
[disabled]="p.isPendingApproval"
|
||||||
|
[routerLink]="p.isPendingApproval ? null : [p.id, 'edit']"
|
||||||
(click)="$event.stopPropagation()"
|
(click)="$event.stopPropagation()"
|
||||||
matTooltip="Edit this product"
|
[matTooltip]="
|
||||||
|
p.isPendingApproval
|
||||||
|
? 'This product is locked because an edit is pending approval'
|
||||||
|
: 'Edit this product'
|
||||||
|
"
|
||||||
*mifosxHasPermission="'UPDATE_CURRENTPRODUCT'"
|
*mifosxHasPermission="'UPDATE_CURRENTPRODUCT'"
|
||||||
>
|
>
|
||||||
<fa-icon icon="edit"></fa-icon><span>Edit</span>
|
<fa-icon [icon]="p.isPendingApproval ? 'lock' : 'edit'"></fa-icon>
|
||||||
|
<span>{{ p.isPendingApproval ? 'Locked' : 'Edit' }}</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
+2
-1
@@ -274,7 +274,8 @@ export class EditFixedDepositProductComponent {
|
|||||||
...this.fixedDepositProduct,
|
...this.fixedDepositProduct,
|
||||||
charts: this.fixedDepositProductCharts,
|
charts: this.fixedDepositProductCharts,
|
||||||
charges: (this.fixedDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
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;
|
delete fixedDepositProduct.advancedAccountingRules;
|
||||||
this.productsService
|
this.productsService
|
||||||
|
|||||||
@@ -137,12 +137,19 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="pl-action-btn pl-action-btn--edit"
|
class="pl-action-btn pl-action-btn--edit"
|
||||||
[routerLink]="[p.id, 'edit']"
|
[class.pl-action-btn--disabled]="p.isPendingApproval"
|
||||||
|
[disabled]="p.isPendingApproval"
|
||||||
|
[routerLink]="p.isPendingApproval ? null : [p.id, 'edit']"
|
||||||
(click)="$event.stopPropagation()"
|
(click)="$event.stopPropagation()"
|
||||||
matTooltip="Edit this product"
|
[matTooltip]="
|
||||||
|
p.isPendingApproval
|
||||||
|
? 'This product is locked because an edit is pending approval'
|
||||||
|
: 'Edit this product'
|
||||||
|
"
|
||||||
*mifosxHasPermission="'UPDATE_FIXEDDEPOSITPRODUCT'"
|
*mifosxHasPermission="'UPDATE_FIXEDDEPOSITPRODUCT'"
|
||||||
>
|
>
|
||||||
<fa-icon icon="edit"></fa-icon><span>Edit</span>
|
<fa-icon [icon]="p.isPendingApproval ? 'lock' : 'edit'"></fa-icon>
|
||||||
|
<span>{{ p.isPendingApproval ? 'Locked' : 'Edit' }}</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -74,6 +74,26 @@ export class ProductsService {
|
|||||||
return this.http.put(`/savingsproducts/${savingProductId}`, savingProduct);
|
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<any> {
|
||||||
|
return this.http.get('/savingsproducts/pending');
|
||||||
|
}
|
||||||
|
|
||||||
|
getPendingSavingsProductEdit(savingsProductId: string): Observable<any> {
|
||||||
|
return this.http.get(`/savingsproducts/${savingsProductId}/pending`);
|
||||||
|
}
|
||||||
|
|
||||||
|
approveSavingsProductEdit(savingsProductId: string): Observable<any> {
|
||||||
|
return this.http.post(`/savingsproducts/${savingsProductId}/approve`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectSavingsProductEdit(savingsProductId: string, rejectReason: string): Observable<any> {
|
||||||
|
return this.http.post(`/savingsproducts/${savingsProductId}/reject`, { rejectReason });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<any>} Current products data
|
* @returns {Observable<any>} Current products data
|
||||||
*/
|
*/
|
||||||
@@ -102,6 +122,26 @@ export class ProductsService {
|
|||||||
return this.http.delete(`/currentproducts/${currentProductId}`);
|
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<any> {
|
||||||
|
return this.http.get('/currentproducts/pending');
|
||||||
|
}
|
||||||
|
|
||||||
|
getPendingCurrentProductEdit(currentProductId: string): Observable<any> {
|
||||||
|
return this.http.get(`/currentproducts/${currentProductId}/pending`);
|
||||||
|
}
|
||||||
|
|
||||||
|
approveCurrentProductEdit(currentProductId: string): Observable<any> {
|
||||||
|
return this.http.post(`/currentproducts/${currentProductId}/approve`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectCurrentProductEdit(currentProductId: string, rejectReason: string): Observable<any> {
|
||||||
|
return this.http.post(`/currentproducts/${currentProductId}/reject`, { rejectReason });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<any>} Share products data
|
* @returns {Observable<any>} Share products data
|
||||||
*/
|
*/
|
||||||
@@ -126,6 +166,26 @@ export class ProductsService {
|
|||||||
return this.http.put(`/products/share/${shareProductId}`, shareProduct);
|
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<any> {
|
||||||
|
return this.http.get('/products/share/pending');
|
||||||
|
}
|
||||||
|
|
||||||
|
getPendingShareProductEdit(shareProductId: string): Observable<any> {
|
||||||
|
return this.http.get(`/products/share/${shareProductId}/pending`);
|
||||||
|
}
|
||||||
|
|
||||||
|
approveShareProductEdit(shareProductId: string): Observable<any> {
|
||||||
|
return this.http.post(`/products/share/${shareProductId}/approve`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectShareProductEdit(shareProductId: string, rejectReason: string): Observable<any> {
|
||||||
|
return this.http.post(`/products/share/${shareProductId}/reject`, { rejectReason });
|
||||||
|
}
|
||||||
|
|
||||||
getDividends(shareProductId: string): Observable<any> {
|
getDividends(shareProductId: string): Observable<any> {
|
||||||
return this.http.get(`/shareproduct/${shareProductId}/dividend`);
|
return this.http.get(`/shareproduct/${shareProductId}/dividend`);
|
||||||
}
|
}
|
||||||
@@ -251,6 +311,26 @@ export class ProductsService {
|
|||||||
return this.http.put(`/fixeddepositproducts/${fixedDepositProductId}`, fixedDepositProduct);
|
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<any> {
|
||||||
|
return this.http.get('/fixeddepositproducts/pending');
|
||||||
|
}
|
||||||
|
|
||||||
|
getPendingFixedDepositProductEdit(fixedDepositProductId: string): Observable<any> {
|
||||||
|
return this.http.get(`/fixeddepositproducts/${fixedDepositProductId}/pending`);
|
||||||
|
}
|
||||||
|
|
||||||
|
approveFixedDepositProductEdit(fixedDepositProductId: string): Observable<any> {
|
||||||
|
return this.http.post(`/fixeddepositproducts/${fixedDepositProductId}/approve`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectFixedDepositProductEdit(fixedDepositProductId: string, rejectReason: string): Observable<any> {
|
||||||
|
return this.http.post(`/fixeddepositproducts/${fixedDepositProductId}/reject`, { rejectReason });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Observable<any>} Tax Components data
|
* @returns {Observable<any>} Tax Components data
|
||||||
*/
|
*/
|
||||||
@@ -526,6 +606,26 @@ export class ProductsService {
|
|||||||
return this.http.put(`/recurringdepositproducts/${recurringDepositProductId}`, recurringDepositProduct);
|
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<any> {
|
||||||
|
return this.http.get('/recurringdepositproducts/pending');
|
||||||
|
}
|
||||||
|
|
||||||
|
getPendingRecurringDepositProductEdit(recurringDepositProductId: string): Observable<any> {
|
||||||
|
return this.http.get(`/recurringdepositproducts/${recurringDepositProductId}/pending`);
|
||||||
|
}
|
||||||
|
|
||||||
|
approveRecurringDepositProductEdit(recurringDepositProductId: string): Observable<any> {
|
||||||
|
return this.http.post(`/recurringdepositproducts/${recurringDepositProductId}/approve`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
rejectRecurringDepositProductEdit(recurringDepositProductId: string, rejectReason: string): Observable<any> {
|
||||||
|
return this.http.post(`/recurringdepositproducts/${recurringDepositProductId}/reject`, { rejectReason });
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the active CBS base interest-rate version(s). */
|
/** Returns the active CBS base interest-rate version(s). */
|
||||||
getAllRdInterestRates(): Observable<any> {
|
getAllRdInterestRates(): Observable<any> {
|
||||||
return this.getRdBaseInterestRates();
|
return this.getRdBaseInterestRates();
|
||||||
|
|||||||
+7
-3
@@ -176,7 +176,11 @@ export class EditRecurringDepositProductComponent {
|
|||||||
...this.recurringDepositProductSettingsStep.recurringDepositProductSettings,
|
...this.recurringDepositProductSettingsStep.recurringDepositProductSettings,
|
||||||
...this.recurringDepositProductChargesStep.recurringDepositProductCharges,
|
...this.recurringDepositProductChargesStep.recurringDepositProductCharges,
|
||||||
...this.recurringDepositProductAccountingStep.recurringDepositProductAccounting,
|
...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
|
rdSlabs: this.activeBaseSlabs
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -240,9 +244,9 @@ export class EditRecurringDepositProductComponent {
|
|||||||
submit() {
|
submit() {
|
||||||
const recurringDepositProduct: any = this.pruneEmptyValues({
|
const recurringDepositProduct: any = this.pruneEmptyValues({
|
||||||
...this.recurringDepositProduct,
|
...this.recurringDepositProduct,
|
||||||
charts: this.recurringDepositProductCharts,
|
|
||||||
charges: (this.recurringDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
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) {
|
if (!recurringDepositProduct.description) {
|
||||||
recurringDepositProduct.description = '';
|
recurringDepositProduct.description = '';
|
||||||
|
|||||||
+5
-1
@@ -91,7 +91,11 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
|
|||||||
otherGlMappingsMap: this.recurringDepositProductsTemplate?.otherGlMappingsMap
|
otherGlMappingsMap: this.recurringDepositProductsTemplate?.otherGlMappingsMap
|
||||||
};
|
};
|
||||||
const accountingMappingOptions = this.recurringDepositProductsTemplate?.accountingMappingOptions || {};
|
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.assetAccountOptions = accountingMappingOptions.assetAccountOptions || [];
|
||||||
this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || [];
|
this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || [];
|
||||||
|
|||||||
+10
-3
@@ -147,12 +147,19 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="pl-action-btn pl-action-btn--edit"
|
class="pl-action-btn pl-action-btn--edit"
|
||||||
[routerLink]="[p.id, 'edit']"
|
[class.pl-action-btn--disabled]="p.isPendingApproval"
|
||||||
|
[disabled]="p.isPendingApproval"
|
||||||
|
[routerLink]="p.isPendingApproval ? null : [p.id, 'edit']"
|
||||||
(click)="$event.stopPropagation()"
|
(click)="$event.stopPropagation()"
|
||||||
matTooltip="Edit this product"
|
[matTooltip]="
|
||||||
|
p.isPendingApproval
|
||||||
|
? 'This product is locked because an edit is pending approval'
|
||||||
|
: 'Edit this product'
|
||||||
|
"
|
||||||
*mifosxHasPermission="'UPDATE_RECURRINGDEPOSITPRODUCT'"
|
*mifosxHasPermission="'UPDATE_RECURRINGDEPOSITPRODUCT'"
|
||||||
>
|
>
|
||||||
<fa-icon icon="edit"></fa-icon><span>Edit</span>
|
<fa-icon [icon]="p.isPendingApproval ? 'lock' : 'edit'"></fa-icon>
|
||||||
|
<span>{{ p.isPendingApproval ? 'Locked' : 'Edit' }}</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -145,13 +145,19 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="sp-action-btn sp-action-btn--edit"
|
class="sp-action-btn sp-action-btn--edit"
|
||||||
[routerLink]="[p.id, 'edit']"
|
[class.sp-action-btn--disabled]="p.isPendingApproval"
|
||||||
|
[disabled]="p.isPendingApproval"
|
||||||
|
[routerLink]="p.isPendingApproval ? null : [p.id, 'edit']"
|
||||||
(click)="$event.stopPropagation()"
|
(click)="$event.stopPropagation()"
|
||||||
matTooltip="Edit this product"
|
[matTooltip]="
|
||||||
|
p.isPendingApproval
|
||||||
|
? 'This product is locked because an edit is pending approval'
|
||||||
|
: 'Edit this product'
|
||||||
|
"
|
||||||
*mifosxHasPermission="'UPDATE_SAVINGSPRODUCT'"
|
*mifosxHasPermission="'UPDATE_SAVINGSPRODUCT'"
|
||||||
>
|
>
|
||||||
<fa-icon icon="edit"></fa-icon>
|
<fa-icon [icon]="p.isPendingApproval ? 'lock' : 'edit'"></fa-icon>
|
||||||
<span>Edit</span>
|
<span>{{ p.isPendingApproval ? 'Locked' : 'Edit' }}</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -481,6 +481,14 @@
|
|||||||
border-color: var(--sp-amber);
|
border-color: var(--sp-amber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--disabled,
|
||||||
|
&--disabled:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #94a3b8;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* No match strip */
|
/* No match strip */
|
||||||
|
|||||||
@@ -135,12 +135,19 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="pl-action-btn pl-action-btn--edit"
|
class="pl-action-btn pl-action-btn--edit"
|
||||||
[routerLink]="[p.id, 'edit']"
|
[class.pl-action-btn--disabled]="p.isPendingApproval"
|
||||||
|
[disabled]="p.isPendingApproval"
|
||||||
|
[routerLink]="p.isPendingApproval ? null : [p.id, 'edit']"
|
||||||
(click)="$event.stopPropagation()"
|
(click)="$event.stopPropagation()"
|
||||||
matTooltip="Edit this product"
|
[matTooltip]="
|
||||||
|
p.isPendingApproval
|
||||||
|
? 'This product is locked because an edit is pending approval'
|
||||||
|
: 'Edit this product'
|
||||||
|
"
|
||||||
*mifosxHasPermission="'UPDATE_SHAREPRODUCT'"
|
*mifosxHasPermission="'UPDATE_SHAREPRODUCT'"
|
||||||
>
|
>
|
||||||
<fa-icon icon="edit"></fa-icon><span>Edit</span>
|
<fa-icon [icon]="p.isPendingApproval ? 'lock' : 'edit'"></fa-icon>
|
||||||
|
<span>{{ p.isPendingApproval ? 'Locked' : 'Edit' }}</span>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
import { PopoverService } from '../../configuration-wizard/popover/popover.service';
|
import { PopoverService } from '../../configuration-wizard/popover/popover.service';
|
||||||
import { ConfigurationWizardService } from '../../configuration-wizard/configuration-wizard.service';
|
import { ConfigurationWizardService } from '../../configuration-wizard/configuration-wizard.service';
|
||||||
|
import { ProductsService } from 'app/products/products.service';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
@@ -60,6 +61,7 @@ export class ShareProductsComponent implements OnInit, AfterViewInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private configurationWizardService = inject(ConfigurationWizardService);
|
private configurationWizardService = inject(ConfigurationWizardService);
|
||||||
private popoverService = inject(PopoverService);
|
private popoverService = inject(PopoverService);
|
||||||
|
private productsService = inject(ProductsService);
|
||||||
|
|
||||||
shareProductsData: any[] = [];
|
shareProductsData: any[] = [];
|
||||||
displayedColumns: string[] = [
|
displayedColumns: string[] = [
|
||||||
@@ -101,6 +103,18 @@ export class ShareProductsComponent implements OnInit, AfterViewInit {
|
|||||||
constructor() {
|
constructor() {
|
||||||
this.route.data.subscribe((data: { shareProducts: any }) => {
|
this.route.data.subscribe((data: { shareProducts: any }) => {
|
||||||
this.shareProductsData = data.shareProducts.pageItems;
|
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;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,6 +117,7 @@
|
|||||||
[savingsAccount]="savingsAccount"
|
[savingsAccount]="savingsAccount"
|
||||||
[savingsAccountDigital]="savingsAccountDigital"
|
[savingsAccountDigital]="savingsAccountDigital"
|
||||||
[savingsAccountNominees]="savingsAccountCurrencyStep?.nominees ?? []"
|
[savingsAccountNominees]="savingsAccountCurrencyStep?.nominees ?? []"
|
||||||
|
[submitting]="submitting"
|
||||||
(submitEvent)="submit()"
|
(submitEvent)="submit()"
|
||||||
>
|
>
|
||||||
</mifosx-savings-account-preview-step>
|
</mifosx-savings-account-preview-step>
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ export class CreateSavingsAccountComponent {
|
|||||||
selectedTabIndex = 0;
|
selectedTabIndex = 0;
|
||||||
/** Total wizard tabs — Product, Currency, Digital, Preview. */
|
/** Total wizard tabs — Product, Currency, Digital, Preview. */
|
||||||
private readonly totalTabs = 4;
|
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 })
|
@ViewChild(SavingsAccountDetailsStepComponent, { static: true })
|
||||||
savingsAccountDetailsStep: SavingsAccountDetailsStepComponent;
|
savingsAccountDetailsStep: SavingsAccountDetailsStepComponent;
|
||||||
@@ -116,6 +118,9 @@ export class CreateSavingsAccountComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
|
if (this.submitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const detailsStep = this.savingsAccountDetailsStep;
|
const detailsStep = this.savingsAccountDetailsStep;
|
||||||
if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
|
if (detailsStep.selectedAccountStatus === 'JOINT' && detailsStep.totalJointSharePercentage !== 100) {
|
||||||
this.alertService.alert({
|
this.alertService.alert({
|
||||||
@@ -177,6 +182,7 @@ export class CreateSavingsAccountComponent {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.submitting = true;
|
||||||
this.savingsService.createSavingsAccount(savingsAccount).subscribe({
|
this.savingsService.createSavingsAccount(savingsAccount).subscribe({
|
||||||
next: (response: any) => {
|
next: (response: any) => {
|
||||||
this.router.navigate(
|
this.router.navigate(
|
||||||
@@ -188,6 +194,7 @@ export class CreateSavingsAccountComponent {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
error: (error: any) => {
|
error: (error: any) => {
|
||||||
|
this.submitting = false;
|
||||||
const errorCode =
|
const errorCode =
|
||||||
error?.error?.errors?.[0]?.userMessageGlobalisationCode ?? error?.error?.userMessageGlobalisationCode;
|
error?.error?.errors?.[0]?.userMessageGlobalisationCode ?? error?.error?.userMessageGlobalisationCode;
|
||||||
if (errorCode === 'error.msg.savings.account.duplicate.product.for.client') {
|
if (errorCode === 'error.msg.savings.account.duplicate.product.for.client') {
|
||||||
|
|||||||
+8
-7
@@ -1,4 +1,4 @@
|
|||||||
<!--
|
<!--
|
||||||
Copyright since 2025 Mifos Initiative
|
Copyright since 2025 Mifos Initiative
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
@@ -115,12 +115,13 @@
|
|||||||
<input matInput [value]="productTemplate.introducerRequired ? 'Yes' : 'No'" readonly />
|
<input matInput [value]="productTemplate.introducerRequired ? 'Yes' : 'No'" readonly />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="sad-field sad-info-row">
|
<mat-form-field appearance="outline" class="sad-field">
|
||||||
<span class="sad-info-label">Share Linking Enabled</span>
|
<mat-label>Share Linking Enabled</mat-label>
|
||||||
<span class="sad-badge" [class.sad-badge--yes]="productTemplate.shareLinkingEnabled">
|
<mat-select [(ngModel)]="shareLinkingEnabled" [ngModelOptions]="{ standalone: true }">
|
||||||
{{ productTemplate.shareLinkingEnabled ? 'Yes' : 'No' }}
|
<mat-option [value]="true">Yes</mat-option>
|
||||||
</span>
|
<mat-option [value]="false">No</mat-option>
|
||||||
</div>
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+7
@@ -70,6 +70,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
|||||||
@Output() savingsAccountProductTemplate = new EventEmitter();
|
@Output() savingsAccountProductTemplate = new EventEmitter();
|
||||||
|
|
||||||
productTemplate: any = null;
|
productTemplate: any = null;
|
||||||
|
shareLinkingEnabled = false;
|
||||||
accountTypeOptions: string[] = [];
|
accountTypeOptions: string[] = [];
|
||||||
customerSegmentOptions: string[] = [];
|
customerSegmentOptions: string[] = [];
|
||||||
selectedAccountType: string | null = null;
|
selectedAccountType: string | null = null;
|
||||||
@@ -333,6 +334,7 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
|||||||
.getSavingsAccountTemplate(entityId, productId, this.savingsAccountTemplate.groupId ? true : false)
|
.getSavingsAccountTemplate(entityId, productId, this.savingsAccountTemplate.groupId ? true : false)
|
||||||
.subscribe((response: any) => {
|
.subscribe((response: any) => {
|
||||||
this.productTemplate = response;
|
this.productTemplate = response;
|
||||||
|
this.shareLinkingEnabled = !!response.shareLinkingEnabled;
|
||||||
this.accountTypeOptions = this.extractOptions(response.accountTypeOptions, response.accountType);
|
this.accountTypeOptions = this.extractOptions(response.accountTypeOptions, response.accountType);
|
||||||
this.customerSegmentOptions = this.extractOptions(response.customerSegmentOptions, response.customerSegment);
|
this.customerSegmentOptions = this.extractOptions(response.customerSegmentOptions, response.customerSegment);
|
||||||
this.selectedAccountType = response.accountType ?? null;
|
this.selectedAccountType = response.accountType ?? null;
|
||||||
@@ -432,6 +434,9 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
|||||||
this.introducerName = ea.introducerName ?? null;
|
this.introducerName = ea.introducerName ?? null;
|
||||||
this.introducerCif = ea.introducerCifNumber ?? null;
|
this.introducerCif = ea.introducerCifNumber ?? null;
|
||||||
}
|
}
|
||||||
|
if (ea.shareLinkingEnabled !== undefined) {
|
||||||
|
this.shareLinkingEnabled = !!ea.shareLinkingEnabled;
|
||||||
|
}
|
||||||
this.cdr.detectChanges();
|
this.cdr.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -650,6 +655,8 @@ export class SavingsAccountDetailsStepComponent implements OnInit {
|
|||||||
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'dd MMMM yyyy') : null;
|
value.minorDateOfBirth = this.minorDob ? this.dateUtils.formatDate(this.minorDob, 'dd MMMM yyyy') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
value.shareLinkingEnabled = this.shareLinkingEnabled;
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-3
@@ -116,9 +116,18 @@
|
|||||||
<span class="preview-label">Share Linking Enabled</span>
|
<span class="preview-label">Share Linking Enabled</span>
|
||||||
<span
|
<span
|
||||||
class="preview-value preview-badge"
|
class="preview-value preview-badge"
|
||||||
[class.preview-badge--yes]="savingsAccountProductTemplate?.shareLinkingEnabled"
|
[class.preview-badge--yes]="
|
||||||
|
savingsAccount?.shareLinkingEnabled !== undefined
|
||||||
|
? savingsAccount.shareLinkingEnabled
|
||||||
|
: savingsAccountProductTemplate?.shareLinkingEnabled
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ savingsAccountProductTemplate?.shareLinkingEnabled | yesNo }}
|
{{
|
||||||
|
(savingsAccount?.shareLinkingEnabled !== undefined
|
||||||
|
? savingsAccount.shareLinkingEnabled
|
||||||
|
: savingsAccountProductTemplate?.shareLinkingEnabled
|
||||||
|
) | yesNo
|
||||||
|
}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -586,7 +595,13 @@
|
|||||||
<button mat-stroked-button [routerLink]="['../..']">
|
<button mat-stroked-button [routerLink]="['../..']">
|
||||||
{{ 'labels.buttons.Cancel' | translate }}
|
{{ 'labels.buttons.Cancel' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button mat-raised-button color="primary" class="preview-submit-row__primary" (click)="submitEvent.emit()">
|
<button
|
||||||
|
mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
class="preview-submit-row__primary"
|
||||||
|
[disabled]="submitting"
|
||||||
|
(click)="submitEvent.emit()"
|
||||||
|
>
|
||||||
<fa-icon icon="check" class="m-r-10"></fa-icon>
|
<fa-icon icon="check" class="m-r-10"></fa-icon>
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+2
@@ -94,6 +94,8 @@ export class SavingsAccountPreviewStepComponent implements OnChanges {
|
|||||||
@Input() activeClientMembers?: any;
|
@Input() activeClientMembers?: any;
|
||||||
/** Hide internal Cancel/Submit buttons when parent provides its own action bar */
|
/** Hide internal Cancel/Submit buttons when parent provides its own action bar */
|
||||||
@Input() hideButtons = false;
|
@Input() hideButtons = false;
|
||||||
|
/** Disables the Submit button while a submission is in flight, preventing duplicate double-click requests */
|
||||||
|
@Input() submitting = false;
|
||||||
|
|
||||||
/** Table Data Source */
|
/** Table Data Source */
|
||||||
dataSource: any;
|
dataSource: any;
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import { SavingsService } from '../savings.service';
|
|||||||
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
|
import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { Currency } from 'app/shared/models/general.model';
|
import { Currency } from 'app/shared/models/general.model';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
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 */
|
/** Environment Configuration */
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
@@ -75,6 +78,9 @@ export class SavingsAccountViewComponent implements OnInit {
|
|||||||
private router = inject(Router);
|
private router = inject(Router);
|
||||||
private savingsService = inject(SavingsService);
|
private savingsService = inject(SavingsService);
|
||||||
private translateService = inject(TranslateService);
|
private translateService = inject(TranslateService);
|
||||||
|
private accountsService = inject(AccountsService);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
dialog = inject(MatDialog);
|
dialog = inject(MatDialog);
|
||||||
|
|
||||||
/** Savings Account Data */
|
/** Savings Account Data */
|
||||||
@@ -124,7 +130,8 @@ export class SavingsAccountViewComponent implements OnInit {
|
|||||||
Close: 'danger',
|
Close: 'danger',
|
||||||
Reject: 'danger',
|
Reject: 'danger',
|
||||||
Delete: 'danger',
|
Delete: 'danger',
|
||||||
'Withdrawn by Client': 'danger'
|
'Withdrawn by Client': 'danger',
|
||||||
|
Terminate: 'danger'
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Lightweight icon fallback for actions whose buttonConfig doesn't
|
/** Lightweight icon fallback for actions whose buttonConfig doesn't
|
||||||
@@ -157,7 +164,8 @@ export class SavingsAccountViewComponent implements OnInit {
|
|||||||
Close: 'times-circle',
|
Close: 'times-circle',
|
||||||
Reject: 'ban',
|
Reject: 'ban',
|
||||||
Delete: 'trash',
|
Delete: 'trash',
|
||||||
'Withdrawn by Client': 'user-slash'
|
'Withdrawn by Client': 'user-slash',
|
||||||
|
Terminate: 'times-circle'
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Returns the colour kind for a given action name (used to tint the
|
/** Returns the colour kind for a given action name (used to tint the
|
||||||
@@ -306,6 +314,9 @@ export class SavingsAccountViewComponent implements OnInit {
|
|||||||
case 'Delete':
|
case 'Delete':
|
||||||
this.deleteSavingsAccount();
|
this.deleteSavingsAccount();
|
||||||
break;
|
break;
|
||||||
|
case 'Terminate':
|
||||||
|
this.terminateSavingsAccount();
|
||||||
|
break;
|
||||||
case 'Calculate Interest':
|
case 'Calculate Interest':
|
||||||
this.calculateInterest();
|
this.calculateInterest();
|
||||||
break;
|
break;
|
||||||
@@ -363,6 +374,35 @@ export class SavingsAccountViewComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminates Savings Account.
|
||||||
|
*/
|
||||||
|
private terminateSavingsAccount() {
|
||||||
|
const terminateSavingsAccountDialogRef = this.dialog.open(ConfirmationDialogComponent, {
|
||||||
|
data: {
|
||||||
|
heading: 'Terminate Account',
|
||||||
|
dialogContext: 'Are you sure you want to terminate this account?',
|
||||||
|
type: 'Strong'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
terminateSavingsAccountDialogRef.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: 'SAVINGS',
|
||||||
|
terminatedOnDate,
|
||||||
|
dateFormat,
|
||||||
|
locale
|
||||||
|
};
|
||||||
|
this.accountsService.terminateAccount(this.savingsAccountData.id, data).subscribe(() => {
|
||||||
|
this.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates savings account's interest
|
* Calculates savings account's interest
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -192,6 +192,10 @@ export class SavingsButtonsConfiguration {
|
|||||||
{
|
{
|
||||||
name: 'Delete',
|
name: 'Delete',
|
||||||
taskPermissionName: 'DELETE_SAVINGSACCOUNT'
|
taskPermissionName: 'DELETE_SAVINGSACCOUNT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Terminate',
|
||||||
|
taskPermissionName: 'DELETE_SAVINGSACCOUNT'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|||||||
+24
-6
@@ -103,6 +103,25 @@ export class CheckerInboxComponent implements OnInit {
|
|||||||
'entity'
|
'entity'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entities whose pending edits are gated by our own m_savings_product_temp lock 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 temp table, leaving its lock stuck forever.
|
||||||
|
* Action them from Approvals -> Pending Product Edits instead, where that's handled correctly.
|
||||||
|
*/
|
||||||
|
private readonly productEditEntities = [
|
||||||
|
'SAVINGSPRODUCT',
|
||||||
|
'CURRENTPRODUCT',
|
||||||
|
'SHAREPRODUCT',
|
||||||
|
'FIXEDDEPOSITPRODUCT',
|
||||||
|
'RECURRINGDEPOSITPRODUCT'
|
||||||
|
];
|
||||||
|
|
||||||
|
private excludeProductEdits(items: any[]): any[] {
|
||||||
|
return (items || []).filter((item: any) => !this.productEditEntities.includes(item?.entityName));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the maker checker data from `resolve`.
|
* Retrieves the maker checker data from `resolve`.
|
||||||
* @param {ActivatedRoute} route Activated Route.
|
* @param {ActivatedRoute} route Activated Route.
|
||||||
@@ -115,11 +134,10 @@ export class CheckerInboxComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.route.data.subscribe((data: { makerCheckerResource: any; makerCheckerTemplate: any }) => {
|
this.route.data.subscribe((data: { makerCheckerResource: any; makerCheckerTemplate: any }) => {
|
||||||
this.searchData = data.makerCheckerResource;
|
const raw = data.makerCheckerResource;
|
||||||
if (this.searchData.length > 0) {
|
this.searchData = this.excludeProductEdits(Array.isArray(raw) ? raw : (raw?.pageItems ?? []));
|
||||||
this.checkerData = true;
|
this.checkerData = this.searchData.length > 0;
|
||||||
}
|
this.makerCheckerTemplate = data.makerCheckerTemplate ?? {};
|
||||||
this.makerCheckerTemplate = data.makerCheckerTemplate;
|
|
||||||
this.dataSource = new MatTableDataSource(this.searchData);
|
this.dataSource = new MatTableDataSource(this.searchData);
|
||||||
this.selection = new SelectionModel(true, []);
|
this.selection = new SelectionModel(true, []);
|
||||||
});
|
});
|
||||||
@@ -157,7 +175,7 @@ export class CheckerInboxComponent implements OnInit {
|
|||||||
makerDateTimeto: this.dateUtils.formatDate(this.makerCheckerSearchForm.value.makerDateTimeto, dateFormat)
|
makerDateTimeto: this.dateUtils.formatDate(this.makerCheckerSearchForm.value.makerDateTimeto, dateFormat)
|
||||||
};
|
};
|
||||||
this.tasksService.getMakerCheckerData(makerCheckerSearchParams).subscribe((response: any) => {
|
this.tasksService.getMakerCheckerData(makerCheckerSearchParams).subscribe((response: any) => {
|
||||||
this.searchData = response;
|
this.searchData = this.excludeProductEdits(response);
|
||||||
if (this.searchData.length === 0) {
|
if (this.searchData.length === 0) {
|
||||||
this.noSearchedData = true;
|
this.noSearchedData = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+4
-2
@@ -82,8 +82,10 @@ export class PendingAccountComponent {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.route.data.subscribe((data: { pendingAccountsData: any }) => {
|
this.route.data.subscribe((data: { pendingAccountsData: any }) => {
|
||||||
// status 100 = submitted and pending approval only; exclude active/approved/etc.
|
// status 100 = submitted and pending approval only; exclude terminated accounts
|
||||||
const items = (data.pendingAccountsData?.pageItems || []).filter((a: any) => a.status?.id === 100);
|
const items = (data.pendingAccountsData?.pageItems || []).filter(
|
||||||
|
(a: any) => a.status?.id === 100 && !a.terminateTrue
|
||||||
|
);
|
||||||
this.showData = items.length > 0;
|
this.showData = items.length > 0;
|
||||||
this.dataSource = new MatTableDataSource(items);
|
this.dataSource = new MatTableDataSource(items);
|
||||||
this.selection = new SelectionModel(true, []);
|
this.selection = new SelectionModel(true, []);
|
||||||
|
|||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
<!--
|
||||||
|
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/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="psp-tab-content">
|
||||||
|
@if (showData) {
|
||||||
|
<div class="psp-action-bar">
|
||||||
|
<mat-form-field appearance="outline" class="psp-search-field">
|
||||||
|
<mat-icon matPrefix>search</mat-icon>
|
||||||
|
<input #filterInput matInput placeholder="Search by product name" (keyup)="applyFilter(filterInput.value)" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<table mat-table [dataSource]="dataSource" class="modern-table">
|
||||||
|
<ng-container matColumnDef="productType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Type</th>
|
||||||
|
<td mat-cell *matCellDef="let edit">
|
||||||
|
<span class="status-badge status-badge--type">{{ edit.productType }}</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="savingsProductName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Product</th>
|
||||||
|
<td mat-cell *matCellDef="let edit">{{ edit.savingsProductName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="makerUsername">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Submitted By</th>
|
||||||
|
<td mat-cell *matCellDef="let edit">{{ edit.makerUsername }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="createdOn">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Submitted On</th>
|
||||||
|
<td mat-cell *matCellDef="let edit">{{ formatDate(edit.createdOn) }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="status">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Status</th>
|
||||||
|
<td mat-cell *matCellDef="let edit">
|
||||||
|
<span class="status-badge status-badge--pending">{{ edit.status }}</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="actions">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Actions</th>
|
||||||
|
<td mat-cell *matCellDef="let edit">
|
||||||
|
<button mat-stroked-button class="psp-btn psp-btn-view" (click)="viewDiff(edit)">
|
||||||
|
<fa-icon icon="eye" class="m-r-10"></fa-icon>Compare
|
||||||
|
</button>
|
||||||
|
<button mat-stroked-button class="psp-btn psp-btn-approve" (click)="approve(edit)">
|
||||||
|
<fa-icon icon="check" class="m-r-10"></fa-icon>Approve
|
||||||
|
</button>
|
||||||
|
<button mat-stroked-button class="psp-btn psp-btn-reject" (click)="reject(edit)">
|
||||||
|
<fa-icon icon="ban" class="m-r-10"></fa-icon>Reject
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: displayedColumns" class="table-row"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (selectedEdit) {
|
||||||
|
<div class="psp-diff-panel">
|
||||||
|
<div class="psp-diff-header">
|
||||||
|
<h3>Comparing "{{ selectedEdit.savingsProductName }}" — Original vs Proposed</h3>
|
||||||
|
<button mat-icon-button (click)="closeDiff()"><fa-icon icon="times"></fa-icon></button>
|
||||||
|
</div>
|
||||||
|
@if (diffRows.length === 0) {
|
||||||
|
<p class="psp-diff-loading">Loading comparison…</p>
|
||||||
|
} @else {
|
||||||
|
<table class="psp-diff-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Original</th>
|
||||||
|
<th>Proposed</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (row of diffRows; track row.label) {
|
||||||
|
<tr [class.psp-diff-row--changed]="row.changed">
|
||||||
|
<td class="psp-diff-label">{{ row.label }}</td>
|
||||||
|
<td class="psp-diff-original">{{ row.original }}</td>
|
||||||
|
<td class="psp-diff-proposed">{{ row.proposed }}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!showData) {
|
||||||
|
<div class="empty-state">
|
||||||
|
<fa-icon icon="hourglass-half" class="empty-icon"></fa-icon>
|
||||||
|
<p class="empty-title">No pending savings product edits</p>
|
||||||
|
<p class="empty-text">There are no savings product edits awaiting approval at this time.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
+180
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
+217
@@ -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<any>;
|
||||||
|
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]));
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
<!--
|
||||||
|
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/.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<div class="cv-tab-content">
|
||||||
|
@if (showData) {
|
||||||
|
<div class="cv-action-bar">
|
||||||
|
<mat-form-field appearance="outline" class="cv-search-field">
|
||||||
|
<mat-icon matPrefix>search</mat-icon>
|
||||||
|
<input matInput placeholder="Search accounts..." (keyup)="applyFilter($event.target['value'])" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<table mat-table [dataSource]="dataSource" class="modern-table">
|
||||||
|
<ng-container matColumnDef="accountNo">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Account No</th>
|
||||||
|
<td mat-cell *matCellDef="let row" class="cell-mono">{{ row.accountNo }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="clientName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Client Name</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.clientName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="productName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Product</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.savingsProductName || row.productName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="accountType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Type</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.accountType }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="officeName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Office</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.officeName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="submittedOnDate">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Submitted On</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ formatDate(row.timeline?.submittedOnDate || row.submittedOnDate) }}</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="status">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Status</th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
<span class="status-badge status-badge--terminated">Terminated</span>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: displayedColumns"
|
||||||
|
(click)="navigateToAccount(row)"
|
||||||
|
class="table-row"
|
||||||
|
></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!showData) {
|
||||||
|
<div class="empty-state">
|
||||||
|
<i class="fas fa-times-circle empty-icon"></i>
|
||||||
|
<p class="empty-title">No terminated accounts</p>
|
||||||
|
<p class="empty-text">Accounts marked for termination will appear here.</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
+17
@@ -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;
|
||||||
|
}
|
||||||
+106
@@ -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<any>;
|
||||||
|
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' });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -94,6 +94,18 @@
|
|||||||
<fa-icon icon="hourglass-half" class="ac-tab__ic"></fa-icon>
|
<fa-icon icon="hourglass-half" class="ac-tab__ic"></fa-icon>
|
||||||
<span>Pending Account</span>
|
<span>Pending Account</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
mat-tab-link
|
||||||
|
class="ac-tab"
|
||||||
|
[routerLink]="['./pending-savings-products']"
|
||||||
|
routerLinkActive
|
||||||
|
#pendingSavingsProducts="routerLinkActive"
|
||||||
|
[active]="pendingSavingsProducts.isActive"
|
||||||
|
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||||
|
>
|
||||||
|
<fa-icon icon="piggy-bank" class="ac-tab__ic"></fa-icon>
|
||||||
|
<span>Pending Product Edits</span>
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
mat-tab-link
|
mat-tab-link
|
||||||
class="ac-tab"
|
class="ac-tab"
|
||||||
@@ -106,6 +118,18 @@
|
|||||||
<fa-icon icon="ban" class="ac-tab__ic"></fa-icon>
|
<fa-icon icon="ban" class="ac-tab__ic"></fa-icon>
|
||||||
<span>Reject Account</span>
|
<span>Reject Account</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
mat-tab-link
|
||||||
|
class="ac-tab"
|
||||||
|
[routerLink]="['./terminated-account']"
|
||||||
|
routerLinkActive
|
||||||
|
#terminatedAccount="routerLinkActive"
|
||||||
|
[active]="terminatedAccount.isActive"
|
||||||
|
*mifosxHasPermission="'ALL_FUNCTIONS_READ'"
|
||||||
|
>
|
||||||
|
<fa-icon icon="times-circle" class="ac-tab__ic"></fa-icon>
|
||||||
|
<span>Terminated Account</span>
|
||||||
|
</a>
|
||||||
<a
|
<a
|
||||||
mat-tab-link
|
mat-tab-link
|
||||||
class="ac-tab"
|
class="ac-tab"
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* 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 { catchError, map } from 'rxjs/operators';
|
||||||
|
import { ProductsService } from 'app/products/products.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges pending edits across all product types sharing the m_savings_product_temp backing table -
|
||||||
|
* Savings, Current Account, Share, Fixed Deposit, and Recurring Deposit products - into one list for
|
||||||
|
* the Pending Product Edits screen.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class GetPendingSavingsProducts implements Resolve<any> {
|
||||||
|
private productsService = inject(ProductsService);
|
||||||
|
|
||||||
|
resolve(): Observable<any> {
|
||||||
|
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' }))
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<any> {
|
||||||
|
private tasksService = inject(TasksService);
|
||||||
|
|
||||||
|
resolve(): Observable<any> {
|
||||||
|
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<number, string> = {};
|
||||||
|
(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
|
||||||
|
] };
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 { 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 { 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 { 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';
|
import { ViewCheckerInboxComponent } from './view-checker-inbox/view-checker-inbox.component';
|
||||||
|
|
||||||
/** Custom Resolvers */
|
/** Custom Resolvers */
|
||||||
@@ -38,6 +40,8 @@ import { MakerCheckerTemplate } from './common-resolvers/makerCheckerTemplate.re
|
|||||||
import { GetCheckerInboxDetailResolver } from './common-resolvers/getCheckerInboxDetail.resolver';
|
import { GetCheckerInboxDetailResolver } from './common-resolvers/getCheckerInboxDetail.resolver';
|
||||||
import { GetPendingAccounts } from './common-resolvers/getPendingAccounts.resolver';
|
import { GetPendingAccounts } from './common-resolvers/getPendingAccounts.resolver';
|
||||||
import { GetRejectedAccounts } from './common-resolvers/getRejectedAccounts.resolver';
|
import { GetRejectedAccounts } from './common-resolvers/getRejectedAccounts.resolver';
|
||||||
|
import { GetTerminatedAccounts } from './common-resolvers/getTerminatedAccounts.resolver';
|
||||||
|
import { GetPendingSavingsProducts } from './common-resolvers/getPendingSavingsProducts.resolver';
|
||||||
|
|
||||||
/** Tasks Routes */
|
/** Tasks Routes */
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
@@ -105,6 +109,22 @@ const routes: Routes = [
|
|||||||
rejectedAccountsData: GetRejectedAccounts
|
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',
|
path: 'withdrawal-requests',
|
||||||
component: WithdrawalRequestsComponent,
|
component: WithdrawalRequestsComponent,
|
||||||
@@ -175,7 +195,9 @@ const routes: Routes = [
|
|||||||
MakerCheckerTemplate,
|
MakerCheckerTemplate,
|
||||||
GetCheckerInboxDetailResolver,
|
GetCheckerInboxDetailResolver,
|
||||||
GetPendingAccounts,
|
GetPendingAccounts,
|
||||||
GetRejectedAccounts
|
GetRejectedAccounts,
|
||||||
|
GetTerminatedAccounts,
|
||||||
|
GetPendingSavingsProducts
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class TasksRoutingModule {}
|
export class TasksRoutingModule {}
|
||||||
|
|||||||
@@ -140,6 +140,16 @@ export class TasksService {
|
|||||||
return this.http.get('/currentaccounts', { params: httpParams });
|
return this.http.get('/currentaccounts', { params: httpParams });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTerminatedSavingsAccounts(): Observable<any> {
|
||||||
|
const httpParams = new HttpParams().set('limit', '1000').set('status', 'terminated');
|
||||||
|
return this.http.get('/savingsaccounts', { params: httpParams });
|
||||||
|
}
|
||||||
|
|
||||||
|
getTerminatedCurrentAccounts(): Observable<any> {
|
||||||
|
const httpParams = new HttpParams().set('limit', '1000').set('status', 'terminated');
|
||||||
|
return this.http.get('/currentaccounts', { params: httpParams });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Approve a savings account
|
* Approve a savings account
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -6,17 +6,26 @@
|
|||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<div class="layout-row align-end gap-1percent layout-lt-md-column container m-b-20">
|
@if (isProductEditEntry) {
|
||||||
<button mat-raised-button color="success" (click)="approveChecker()">
|
<div class="container m-b-20">
|
||||||
<fa-icon icon="check" class="m-r-10"></fa-icon>{{ 'labels.buttons.Approve' | translate }}
|
<div class="mat-elevation-z1" style="padding: 12px 16px; border-left: 4px solid #f59e0b; background: #fffbeb">
|
||||||
</button>
|
This is a product edit awaiting approval. Approve or reject it from
|
||||||
<button mat-raised-button color="warn" (click)="deleteChecker()">
|
<strong>Approvals > Pending Product Edits</strong> instead — actioning it here would not release its lock.
|
||||||
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
|
</div>
|
||||||
</button>
|
</div>
|
||||||
<button mat-raised-button color="reject" (click)="rejectChecker()">
|
} @else {
|
||||||
<fa-icon icon="times" class="m-r-10"></fa-icon>{{ 'labels.buttons.Reject' | translate }}
|
<div class="layout-row align-end gap-1percent layout-lt-md-column container m-b-20">
|
||||||
</button>
|
<button mat-raised-button color="success" (click)="approveChecker()">
|
||||||
</div>
|
<fa-icon icon="check" class="m-r-10"></fa-icon>{{ 'labels.buttons.Approve' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="warn" (click)="deleteChecker()">
|
||||||
|
<fa-icon icon="trash" class="m-r-10"></fa-icon>{{ 'labels.buttons.Delete' | translate }}
|
||||||
|
</button>
|
||||||
|
<button mat-raised-button color="reject" (click)="rejectChecker()">
|
||||||
|
<fa-icon icon="times" class="m-r-10"></fa-icon>{{ 'labels.buttons.Reject' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<mat-card>
|
<mat-card>
|
||||||
|
|||||||
@@ -50,6 +50,24 @@ export class ViewCheckerInboxComponent {
|
|||||||
/** Checks if there is any object or not in jsondata */
|
/** Checks if there is any object or not in jsondata */
|
||||||
displayJSONData = false;
|
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`.
|
* Retrieves the maker checker id data from `resolve`.
|
||||||
* @param {ActivatedRoute} route Activated Route.
|
* @param {ActivatedRoute} route Activated Route.
|
||||||
|
|||||||
@@ -540,6 +540,14 @@
|
|||||||
border-color: var(--pl-amber);
|
border-color: var(--pl-amber);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--disabled,
|
||||||
|
&--disabled:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #94a3b8;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* No-match strip */
|
/* No-match strip */
|
||||||
|
|||||||
Reference in New Issue
Block a user