feat(deposits): sync linked account with maturity terms, add deposit certificate printing
Fixed and Recurring Deposit wizards now bidirectionally sync the Product tab's linked-account selection with the new Terms tab's Transfer-to-Savings account, disable the Withdraw Deposit maturity instruction end-to-end, and card-style the Transfer to Savings picker to match the existing linked-account UI. Adds a printable FD/RD deposit certificate from the account view page, following the existing letter-mgmt certificate pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+36
-1
@@ -33,6 +33,7 @@
|
||||
<mifosx-fixed-deposit-account-details-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
(fixedDepositsAccountProductTemplate)="setTemplate($event)"
|
||||
[externalLinkedAccountId]="fixedDepositAccountMaturityTermsStep?.selectedTransferAccount?.id ?? null"
|
||||
[hideButtons]="true"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-details-step>
|
||||
@@ -107,7 +108,40 @@
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 4: Preview -->
|
||||
<!-- Tab 4: Terms -->
|
||||
<mat-tab [disabled]="!fixedDepositsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="file-alt" class="cfa-tab__icon"></fa-icon>
|
||||
Terms
|
||||
</ng-template>
|
||||
<div class="cfa-tab-body">
|
||||
@if (fixedDepositsAccountProductTemplate) {
|
||||
<mifosx-fixed-deposit-account-maturity-terms-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
[linkedSavingsAccountId]="
|
||||
fixedDepositsAccountDetailsStep?.selectedLinkedAccount?.accountType === 'SAVINGS'
|
||||
? fixedDepositsAccountDetailsStep?.selectedLinkedAccount?.id
|
||||
: null
|
||||
"
|
||||
></mifosx-fixed-deposit-account-maturity-terms-step>
|
||||
}
|
||||
|
||||
<div class="cfa-actions">
|
||||
<button mat-stroked-button (click)="prev()">
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<span class="cfa-actions__spacer"></span>
|
||||
<button mat-raised-button color="primary" (click)="next()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 5: Preview -->
|
||||
<mat-tab [disabled]="!fixedDepositsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="eye" class="cfa-tab__icon"></fa-icon>
|
||||
@@ -121,6 +155,7 @@
|
||||
[fixedDepositAccountData]="fixedDepositAccount"
|
||||
[selectedTenure]="selectedTenure"
|
||||
[fdNominees]="fixedDepositAccountCurrencyStep?.nominees ?? []"
|
||||
[closureSettings]="fixedDepositAccountMaturityTermsStep?.closureSettings ?? {}"
|
||||
[hideButtons]="true"
|
||||
(submitEvent)="submit()"
|
||||
></mifosx-fixed-deposit-account-preview-step>
|
||||
|
||||
+9
-2
@@ -24,6 +24,7 @@ import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { FixedDepositAccountInterestRateChartStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-interest-rate-chart-step/fixed-deposit-account-interest-rate-chart-step.component';
|
||||
import { FixedDepositAccountPreviewStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-preview-step/fixed-deposit-account-preview-step.component';
|
||||
import { FixedDepositAccountMaturityTermsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-maturity-terms-step/fixed-deposit-account-maturity-terms-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
@@ -40,6 +41,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
FixedDepositAccountDetailsStepComponent,
|
||||
FixedDepositAccountInterestRateChartStepComponent,
|
||||
FixedDepositAccountCurrencyStepComponent,
|
||||
FixedDepositAccountMaturityTermsStepComponent,
|
||||
FixedDepositAccountPreviewStepComponent
|
||||
]
|
||||
})
|
||||
@@ -56,6 +58,10 @@ export class CreateFixedDepositAccountComponent {
|
||||
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
|
||||
@ViewChild(FixedDepositAccountCurrencyStepComponent)
|
||||
fixedDepositAccountCurrencyStep: FixedDepositAccountCurrencyStepComponent;
|
||||
@ViewChild(FixedDepositAccountPreviewStepComponent)
|
||||
fixedDepositAccountPreviewStep: FixedDepositAccountPreviewStepComponent;
|
||||
@ViewChild(FixedDepositAccountMaturityTermsStepComponent)
|
||||
fixedDepositAccountMaturityTermsStep: FixedDepositAccountMaturityTermsStepComponent;
|
||||
|
||||
/** Fixed Deposits Account Template */
|
||||
fixedDepositsAccountTemplate: any;
|
||||
@@ -63,8 +69,8 @@ export class CreateFixedDepositAccountComponent {
|
||||
fixedDepositsAccountProductTemplate: any;
|
||||
/** Currently visible wizard tab (0-indexed). */
|
||||
selectedTabIndex = 0;
|
||||
/** Total wizard tabs — Product, Currency, Chart, Preview. */
|
||||
private readonly totalTabs = 4;
|
||||
/** Total wizard tabs — Product, Currency, Chart, Terms, Preview. */
|
||||
private readonly totalTabs = 5;
|
||||
/** Active CBS base interest rate versions for the selected product's FD type */
|
||||
cbsBaseRates: any[] = [];
|
||||
/** Tenure selected from the interest rate chart step */
|
||||
@@ -243,6 +249,7 @@ export class CreateFixedDepositAccountComponent {
|
||||
...this.fixedDepositAccount,
|
||||
...terms,
|
||||
...settings,
|
||||
...(this.fixedDepositAccountMaturityTermsStep?.closureSettings ?? {}),
|
||||
...(this.selectedTenure
|
||||
? {
|
||||
depositPeriod: this.selectedTenure.depositPeriod,
|
||||
|
||||
+36
-1
@@ -35,6 +35,7 @@
|
||||
<mifosx-fixed-deposit-account-details-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountAndTemplate"
|
||||
(fixedDepositsAccountProductTemplate)="setTemplate($event)"
|
||||
[externalLinkedAccountId]="fixedDepositAccountMaturityTermsStep?.selectedTransferAccount?.id ?? null"
|
||||
[hideButtons]="true"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-details-step>
|
||||
@@ -111,7 +112,40 @@
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 4: Preview -->
|
||||
<!-- Tab 4: Terms -->
|
||||
<mat-tab [disabled]="!fixedDepositsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="file-alt" class="cfa-tab__icon"></fa-icon>
|
||||
Terms
|
||||
</ng-template>
|
||||
<div class="cfa-tab-body">
|
||||
@if (fixedDepositsAccountProductTemplate) {
|
||||
<mifosx-fixed-deposit-account-maturity-terms-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountAndTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
[linkedSavingsAccountId]="
|
||||
fixedDepositsAccountDetailsStep?.selectedLinkedAccount?.accountType === 'SAVINGS'
|
||||
? fixedDepositsAccountDetailsStep?.selectedLinkedAccount?.id
|
||||
: null
|
||||
"
|
||||
></mifosx-fixed-deposit-account-maturity-terms-step>
|
||||
}
|
||||
|
||||
<div class="cfa-actions">
|
||||
<button mat-stroked-button (click)="prev()">
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<span class="cfa-actions__spacer"></span>
|
||||
<button mat-raised-button color="primary" (click)="next()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 5: Preview -->
|
||||
<mat-tab [disabled]="!fixedDepositsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="eye" class="cfa-tab__icon"></fa-icon>
|
||||
@@ -125,6 +159,7 @@
|
||||
[fixedDepositAccountData]="fixedDepositAccount"
|
||||
[selectedTenure]="selectedTenure"
|
||||
[fdNominees]="fixedDepositAccountCurrencyStep?.nominees ?? []"
|
||||
[closureSettings]="fixedDepositAccountMaturityTermsStep?.closureSettings ?? {}"
|
||||
[hideButtons]="true"
|
||||
(submitEvent)="submit()"
|
||||
></mifosx-fixed-deposit-account-preview-step>
|
||||
|
||||
+8
-1
@@ -25,6 +25,7 @@ import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { FixedDepositAccountInterestRateChartStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-interest-rate-chart-step/fixed-deposit-account-interest-rate-chart-step.component';
|
||||
import { FixedDepositAccountPreviewStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-preview-step/fixed-deposit-account-preview-step.component';
|
||||
import { FixedDepositAccountMaturityTermsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-maturity-terms-step/fixed-deposit-account-maturity-terms-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
@@ -41,6 +42,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
FixedDepositAccountDetailsStepComponent,
|
||||
FixedDepositAccountInterestRateChartStepComponent,
|
||||
FixedDepositAccountCurrencyStepComponent,
|
||||
FixedDepositAccountMaturityTermsStepComponent,
|
||||
FixedDepositAccountPreviewStepComponent
|
||||
]
|
||||
})
|
||||
@@ -58,6 +60,10 @@ export class EditFixedDepositAccountComponent {
|
||||
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
|
||||
@ViewChild(FixedDepositAccountCurrencyStepComponent)
|
||||
fixedDepositAccountCurrencyStep: FixedDepositAccountCurrencyStepComponent;
|
||||
@ViewChild(FixedDepositAccountPreviewStepComponent)
|
||||
fixedDepositAccountPreviewStep: FixedDepositAccountPreviewStepComponent;
|
||||
@ViewChild(FixedDepositAccountMaturityTermsStepComponent)
|
||||
fixedDepositAccountMaturityTermsStep: FixedDepositAccountMaturityTermsStepComponent;
|
||||
|
||||
/** Full account data + template from resolver */
|
||||
fixedDepositsAccountAndTemplate: any;
|
||||
@@ -65,7 +71,7 @@ export class EditFixedDepositAccountComponent {
|
||||
fixedDepositsAccountProductTemplate: any;
|
||||
/** Currently visible wizard tab (0-indexed). */
|
||||
selectedTabIndex = 0;
|
||||
private readonly totalTabs = 4;
|
||||
private readonly totalTabs = 5;
|
||||
/** Active CBS base interest rate versions for the selected product's FD type */
|
||||
cbsBaseRates: any[] = [];
|
||||
/** Tenure selected from the interest rate chart step */
|
||||
@@ -236,6 +242,7 @@ export class EditFixedDepositAccountComponent {
|
||||
...this.fixedDepositAccount,
|
||||
...terms,
|
||||
...settings,
|
||||
...(this.fixedDepositAccountMaturityTermsStep?.closureSettings ?? {}),
|
||||
...(this.selectedTenure
|
||||
? {
|
||||
depositPeriod: this.selectedTenure.depositPeriod,
|
||||
|
||||
+32
-2
@@ -7,7 +7,17 @@
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, Input, Output, EventEmitter, inject, ChangeDetectorRef } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
inject,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, FormsModule } from '@angular/forms';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
@@ -43,7 +53,7 @@ import { IntroducerSearchComponent } from 'app/shared/introducer-search/introduc
|
||||
IntroducerSearchComponent
|
||||
]
|
||||
})
|
||||
export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
export class FixedDepositAccountDetailsStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private fixedDepositsService = inject(FixedDepositsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
@@ -56,6 +66,9 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
/** Fixed Deposits Account Template */
|
||||
@Input() fixedDepositsAccountTemplate: any;
|
||||
@Input() hideButtons = false;
|
||||
/** Linked-account id picked elsewhere in the wizard (e.g. the Terms step's Transfer to Savings
|
||||
* account) -- pre-fills this card so the officer doesn't have to pick the same account twice. */
|
||||
@Input() externalLinkedAccountId: number | null = null;
|
||||
|
||||
/** Minimum date allowed. */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
@@ -222,6 +235,23 @@ export class FixedDepositAccountDetailsStepComponent implements OnInit {
|
||||
this.dalSearch = '';
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (!changes.externalLinkedAccountId || this.selectedLinkedAccount) {
|
||||
return;
|
||||
}
|
||||
const id = this.externalLinkedAccountId;
|
||||
if (!id) return;
|
||||
const savings = this.dalAllSavings.find((a: any) => a.id === id);
|
||||
const current = this.dalAllCurrent.find((a: any) => a.id === id);
|
||||
if (savings) {
|
||||
this.dalSelectedType = 'SAVINGS';
|
||||
this.selectedLinkedAccount = savings;
|
||||
} else if (current) {
|
||||
this.dalSelectedType = 'CURRENT';
|
||||
this.selectedLinkedAccount = current;
|
||||
}
|
||||
}
|
||||
|
||||
private loadClientAccounts(): void {
|
||||
const clientId = String(this.fixedDepositsAccountTemplate?.clientId);
|
||||
if (!clientId) return;
|
||||
|
||||
+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/.
|
||||
-->
|
||||
|
||||
<form [formGroup]="closureSettingsForm" class="layout-row-wrap gap-2px responsive-column">
|
||||
<h4 class="mat-h4 flex-98">{{ 'labels.heading.For Pre-mature closure' | translate }}</h4>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Maturity Instructions' | translate }}</mat-label>
|
||||
<mat-select formControlName="maturityInstructionId">
|
||||
@for (maturityInstruction of maturityInstructionOptions; track maturityInstruction) {
|
||||
<mat-option [value]="maturityInstruction.id">
|
||||
{{ maturityInstruction.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@if (closureSettingsForm.contains('transferToSavingsId')) {
|
||||
<div class="dal-card flex-98">
|
||||
<div class="dal-card__header">
|
||||
<div class="dal-card__header-left">
|
||||
<div class="dal-card__icon">
|
||||
<mat-icon>savings</mat-icon>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="dal-card__title">{{ 'labels.inputs.Transfer to savings account' | translate }}</h3>
|
||||
<span class="dal-card__sub">Maturity proceeds will be credited to this account</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dal-card__body">
|
||||
@if (selectedTransferAccount) {
|
||||
<div class="dal-selected">
|
||||
<div class="dal-selected__info">
|
||||
<span class="dal-type-badge dal-type-badge--savings">
|
||||
<mat-icon>savings</mat-icon>
|
||||
Savings
|
||||
</span>
|
||||
<div class="dal-selected__details">
|
||||
<span class="dal-selected__accno">{{ selectedTransferAccount.accountNo }}</span>
|
||||
<span class="dal-selected__product">{{ selectedTransferAccount.savingsProductName }}</span>
|
||||
</div>
|
||||
@if (selectedTransferAccount.summary?.accountBalance != null) {
|
||||
<div class="dal-selected__balance">
|
||||
<span class="dal-selected__bal-label">Balance</span>
|
||||
<span class="dal-selected__bal-amount">
|
||||
{{ selectedTransferAccount.currency?.code }}
|
||||
{{ selectedTransferAccount.summary.accountBalance | number: '1.2-2' }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="dal-change-btn"
|
||||
(click)="clearTransferAccount()"
|
||||
>
|
||||
<mat-icon>swap_horiz</mat-icon>
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<mat-form-field appearance="outline" class="flex-98">
|
||||
<mat-label>{{ 'labels.inputs.Transfer to savings account' | translate }}</mat-label>
|
||||
<mat-select formControlName="transferToSavingsId" required>
|
||||
@for (account of savingsAccountsData; track account) {
|
||||
<mat-option [value]="account.id">
|
||||
({{ account.accountNo }}) {{ account.savingsProductName }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Transfer to savings account is required</mat-error>
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-checkbox class="flex-98 margin-v" labelPosition="before" formControlName="preClosurePenalApplicable">
|
||||
{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
@if (closureSettingsForm.value.preClosurePenalApplicable) {
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Penal Interest' | translate }} (%)</mat-label>
|
||||
<input type="number" matInput formControlName="preClosurePenalInterest" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Period' | translate }}</mat-label>
|
||||
<mat-select formControlName="preClosurePenalInterestOnTypeId">
|
||||
@for (preClosurePenalInterestOnType of preClosurePenalInterestOnTypeData; track preClosurePenalInterestOnType) {
|
||||
<mat-option [value]="preClosurePenalInterestOnType.id">
|
||||
{{ preClosurePenalInterestOnType.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
}
|
||||
</form>
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
// ── Transfer-to-Savings account card (mirrors the Deposit Account Link card) ──
|
||||
|
||||
.dal-card {
|
||||
margin-top: 16px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||||
}
|
||||
|
||||
&__header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
background: #0ea5e9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0c4a6e;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.dal-selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px 20px;
|
||||
border: 2px solid #0ea5e9;
|
||||
border-radius: 12px;
|
||||
background: #f0f9ff;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__accno {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0c4a6e;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
&__product {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
&__balance {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__bal-label {
|
||||
font-size: 10px;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
&__bal-amount {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0c4a6e;
|
||||
}
|
||||
}
|
||||
|
||||
.dal-change-btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dal-type-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
border: 1px solid #e2e8f0;
|
||||
|
||||
mat-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&--savings {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
border-color: #bfdbfe;
|
||||
|
||||
mat-icon {
|
||||
color: #2563eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, Input, OnChanges, SimpleChanges, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, UntypedFormControl, Validators } from '@angular/forms';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Fixed Deposit Account Maturity Terms Step -- what happens to this account at maturity
|
||||
* (Withdraw / Transfer to Savings / Reinvest) and the pre-mature-closure penalty, decided
|
||||
* per account rather than silently inherited from the product.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-fixed-deposit-account-maturity-terms-step',
|
||||
templateUrl: './fixed-deposit-account-maturity-terms-step.component.html',
|
||||
styleUrls: ['./fixed-deposit-account-maturity-terms-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatCheckbox,
|
||||
MatIcon
|
||||
]
|
||||
})
|
||||
export class FixedDepositAccountMaturityTermsStepComponent implements OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
|
||||
@Input() fixedDepositsAccountTemplate: any;
|
||||
@Input() fixedDepositsAccountProductTemplate: any;
|
||||
/** Account already linked elsewhere in the wizard (the Details step's Deposit Account Link) --
|
||||
* pre-fills Transfer to Savings so the officer doesn't have to pick the same account twice. */
|
||||
@Input() linkedSavingsAccountId: number | null = null;
|
||||
|
||||
closureSettingsForm: UntypedFormGroup;
|
||||
maturityInstructionOptions: any[] = [];
|
||||
savingsAccountsData: any[] = [];
|
||||
preClosurePenalInterestOnTypeData: any[] = [];
|
||||
|
||||
constructor() {
|
||||
this.closureSettingsForm = this.formBuilder.group({
|
||||
maturityInstructionId: [''],
|
||||
preClosurePenalApplicable: [false],
|
||||
preClosurePenalInterest: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
preClosurePenalInterestOnTypeId: ['']
|
||||
});
|
||||
this.closureSettingsForm.get('maturityInstructionId').valueChanges.subscribe((value: number) => {
|
||||
// 200 = Transfer to Savings -- Withdraw Deposit (100) needs no target account, and both
|
||||
// Reinvest options (300/400) open a brand-new FD instead, so they ignore this field too.
|
||||
if (value === 200) {
|
||||
this.closureSettingsForm.addControl(
|
||||
'transferToSavingsId',
|
||||
new UntypedFormControl(this.linkedSavingsAccountId ?? '', Validators.required)
|
||||
);
|
||||
} else {
|
||||
this.closureSettingsForm.removeControl('transferToSavingsId');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.linkedSavingsAccountId) {
|
||||
const control = this.closureSettingsForm.get('transferToSavingsId');
|
||||
if (control && !control.value && this.linkedSavingsAccountId) {
|
||||
control.setValue(this.linkedSavingsAccountId);
|
||||
}
|
||||
}
|
||||
if (this.fixedDepositsAccountProductTemplate) {
|
||||
// Withdraw Deposit (id 100) books the maturity proceeds out of the bank's cash/vault the moment
|
||||
// the account matures, before the customer has actually collected it -- disabled everywhere.
|
||||
this.maturityInstructionOptions = (
|
||||
this.fixedDepositsAccountProductTemplate.maturityInstructionOptions ?? []
|
||||
).filter((option: any) => option.id !== 100);
|
||||
this.savingsAccountsData = this.fixedDepositsAccountProductTemplate.savingsAccounts ?? [];
|
||||
this.preClosurePenalInterestOnTypeData =
|
||||
this.fixedDepositsAccountProductTemplate.preClosurePenalInterestOnTypeOptions ?? [];
|
||||
if (!this.closureSettingsForm.get('preClosurePenalApplicable').dirty) {
|
||||
this.closureSettingsForm.patchValue({
|
||||
preClosurePenalApplicable: this.fixedDepositsAccountProductTemplate.preClosurePenalApplicable ?? false,
|
||||
preClosurePenalInterest: this.fixedDepositsAccountProductTemplate.preClosurePenalInterest ?? '',
|
||||
preClosurePenalInterestOnTypeId:
|
||||
this.fixedDepositsAccountProductTemplate.preClosurePenalInterestOnType?.id ?? ''
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The linked-savings account for the transfer-to-savings maturity instruction, once picked. */
|
||||
get selectedTransferAccount(): any {
|
||||
const id = this.closureSettingsForm.value.transferToSavingsId;
|
||||
if (!id) return null;
|
||||
return this.savingsAccountsData.find((account: any) => account.id === id) ?? null;
|
||||
}
|
||||
|
||||
clearTransferAccount(): void {
|
||||
this.closureSettingsForm.get('transferToSavingsId')?.setValue('');
|
||||
}
|
||||
|
||||
/** Chosen maturity/closure settings, ready to merge into the submit payload. */
|
||||
get closureSettings() {
|
||||
const settings = this.closureSettingsForm.getRawValue();
|
||||
for (const key in settings) {
|
||||
if (settings[key] === '') {
|
||||
delete settings[key];
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
+35
@@ -683,6 +683,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-flag-checkered preview-section__icon"></i>
|
||||
<span class="preview-section__title">Maturity & Pre-mature Closure</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Maturity Instructions' | translate }}</span>
|
||||
<span class="preview-value">{{ closureMaturityInstructionLabel }}</span>
|
||||
</div>
|
||||
@if (closureSettings?.transferToSavingsId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Transfer to savings account' | translate }}</span>
|
||||
<span class="preview-value">{{ closureTransferToSavingsLabel }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}</span>
|
||||
<span class="preview-value">{{ closureSettings?.preClosurePenalApplicable | yesNo }}</span>
|
||||
</div>
|
||||
@if (closureSettings?.preClosurePenalApplicable) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Penal Interest' | translate }} (%)</span>
|
||||
<span class="preview-value">{{ closureSettings?.preClosurePenalInterest }}%</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Period' | translate }}</span>
|
||||
<span class="preview-value">{{ closurePenalInterestOnTypeLabel }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!hideButtons) {
|
||||
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
|
||||
+20
-1
@@ -164,7 +164,26 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
||||
/** Form submission event */
|
||||
@Output() submitEvent = new EventEmitter();
|
||||
|
||||
constructor() {}
|
||||
/** Maturity/pre-mature-closure choices made in the Terms step -- shown here read-only. */
|
||||
@Input() closureSettings: any = {};
|
||||
|
||||
get closureMaturityInstructionLabel(): string {
|
||||
const options = this.fixedDepositsAccountProductTemplate?.maturityInstructionOptions ?? [];
|
||||
const match = options.find((o: any) => o.id === this.closureSettings?.maturityInstructionId);
|
||||
return match?.value ?? '—';
|
||||
}
|
||||
|
||||
get closureTransferToSavingsLabel(): string {
|
||||
const accounts = this.fixedDepositsAccountProductTemplate?.savingsAccounts ?? [];
|
||||
const match = accounts.find((a: any) => a.id === this.closureSettings?.transferToSavingsId);
|
||||
return match ? `(${match.accountNo}) ${match.savingsProductName}` : '—';
|
||||
}
|
||||
|
||||
get closurePenalInterestOnTypeLabel(): string {
|
||||
const options = this.fixedDepositsAccountProductTemplate?.preClosurePenalInterestOnTypeOptions ?? [];
|
||||
const match = options.find((o: any) => o.id === this.closureSettings?.preClosurePenalInterestOnTypeId);
|
||||
return match?.value ?? '—';
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.fixedDepositsAccountProductTemplate) {
|
||||
|
||||
+10
@@ -79,6 +79,16 @@
|
||||
|
||||
<!-- Action menu (kebab) -->
|
||||
<div class="fd-hero__actions">
|
||||
@if (!fixedDepositsAccountData.status.rejected && !fixedDepositsAccountData.status.submittedAndPendingApproval) {
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Print Certificate"
|
||||
(click)="printCertificate()"
|
||||
aria-label="Print certificate"
|
||||
>
|
||||
<fa-icon icon="print"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
<button mat-icon-button [matMenuTriggerFor]="accountMenu" aria-label="Account actions" yPosition="below">
|
||||
<fa-icon icon="ellipsis-v"></fa-icon>
|
||||
</button>
|
||||
|
||||
+15
@@ -16,6 +16,7 @@ import { ConfirmationDialogComponent } from 'app/shared/confirmation-dialog/conf
|
||||
import { CalculateInterestDialogComponent } from './custom-dialogs/calculate-interest-dialog/calculate-interest-dialog.component';
|
||||
import { PostInterestDialogComponent } from './custom-dialogs/post-interest-dialog/post-interest-dialog.component';
|
||||
import { ToggleWithholdTaxDialogComponent } from './custom-dialogs/toggle-withhold-tax-dialog/toggle-withhold-tax-dialog.component';
|
||||
import { DepositCertificateDialogComponent } from 'app/shared/deposit-certificate-dialog/deposit-certificate-dialog.component';
|
||||
|
||||
/** Custom Button Config. */
|
||||
import { FixedDepositsButtonsConfiguration } from './fixed-deposits-buttons.config';
|
||||
@@ -358,6 +359,20 @@ export class FixedDepositAccountViewComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a printable deposit certificate for this account.
|
||||
*/
|
||||
printCertificate() {
|
||||
this.dialog.open(DepositCertificateDialogComponent, {
|
||||
width: '900px',
|
||||
data: {
|
||||
accountType: 'FD',
|
||||
accountData: this.fixedDepositsAccountData,
|
||||
clientId: this.fixedDepositsAccountData.clientId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables withhold tax for fixed deposits account
|
||||
* Fixed deposits endpoint is not supported so using Savings endpoint.
|
||||
|
||||
+36
-1
@@ -33,6 +33,7 @@
|
||||
<mifosx-recurring-deposits-account-details-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
(recurringDepositsAccountProductTemplate)="setTemplate($event)"
|
||||
[externalLinkedAccountId]="recurringDepositsAccountMaturityTermsStep?.selectedTransferAccount?.id ?? null"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-details-step>
|
||||
|
||||
@@ -111,7 +112,40 @@
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 4: Preview -->
|
||||
<!-- Tab 4: Terms -->
|
||||
<mat-tab [disabled]="!recurringDepositsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="file-alt" class="cra-tab__icon"></fa-icon>
|
||||
Terms
|
||||
</ng-template>
|
||||
<div class="cra-tab-body">
|
||||
@if (recurringDepositsAccountProductTemplate) {
|
||||
<mifosx-recurring-deposits-account-maturity-terms-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[linkedSavingsAccountId]="
|
||||
recurringDepositsAccountDetailsStep?.selectedLinkedAccount?.accountType === 'SAVINGS'
|
||||
? recurringDepositsAccountDetailsStep?.selectedLinkedAccount?.id
|
||||
: null
|
||||
"
|
||||
></mifosx-recurring-deposits-account-maturity-terms-step>
|
||||
}
|
||||
|
||||
<div class="cra-actions">
|
||||
<button mat-stroked-button (click)="prev()">
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<span class="cra-actions__spacer"></span>
|
||||
<button mat-raised-button color="primary" (click)="next()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 5: Preview -->
|
||||
<mat-tab [disabled]="!recurringDepositsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="eye" class="cra-tab__icon"></fa-icon>
|
||||
@@ -132,6 +166,7 @@
|
||||
[rdDaysInYear]="rdDaysInYear"
|
||||
[rdAppliedRate]="rdAppliedRate"
|
||||
[rdIsSeniorCitizen]="rdIsSeniorCitizen"
|
||||
[closureSettings]="recurringDepositsAccountMaturityTermsStep?.closureSettings ?? {}"
|
||||
[hideButtons]="true"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
|
||||
+10
-1
@@ -24,6 +24,7 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { RecurringDepositsAccountCurrencyStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component';
|
||||
import { RecurringDepositsAccountInterestRateChartStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component';
|
||||
import { RecurringDepositsAccountPreviewStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component';
|
||||
import { RecurringDepositsAccountMaturityTermsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-maturity-terms-step/recurring-deposits-account-maturity-terms-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
@@ -40,6 +41,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
RecurringDepositsAccountDetailsStepComponent,
|
||||
RecurringDepositsAccountCurrencyStepComponent,
|
||||
RecurringDepositsAccountInterestRateChartStepComponent,
|
||||
RecurringDepositsAccountMaturityTermsStepComponent,
|
||||
RecurringDepositsAccountPreviewStepComponent
|
||||
]
|
||||
})
|
||||
@@ -61,6 +63,12 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
@ViewChild(RecurringDepositsAccountInterestRateChartStepComponent)
|
||||
recurringDepositsAccountChartStep: RecurringDepositsAccountInterestRateChartStepComponent;
|
||||
|
||||
@ViewChild(RecurringDepositsAccountPreviewStepComponent)
|
||||
recurringDepositsAccountPreviewStep: RecurringDepositsAccountPreviewStepComponent;
|
||||
|
||||
@ViewChild(RecurringDepositsAccountMaturityTermsStepComponent)
|
||||
recurringDepositsAccountMaturityTermsStep: RecurringDepositsAccountMaturityTermsStepComponent;
|
||||
|
||||
/** Recurring Deposits Account Template */
|
||||
recurringDepositsAccountTemplate: any;
|
||||
/** Recurring Deposit Account Product Template */
|
||||
@@ -68,7 +76,7 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
/** Currently visible wizard tab (0-indexed). */
|
||||
selectedTabIndex = 0;
|
||||
/** Total wizard tabs — Product, Currency, Chart, Preview. */
|
||||
private readonly totalTabs = 4;
|
||||
private readonly totalTabs = 5;
|
||||
/** Active CBS base interest rate versions */
|
||||
cbsBaseRates: any[] = [];
|
||||
/** Selected tenure value and frequency from calculator */
|
||||
@@ -258,6 +266,7 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
...details,
|
||||
...terms,
|
||||
...settings,
|
||||
...(this.recurringDepositsAccountMaturityTermsStep?.closureSettings ?? {}),
|
||||
...productDefaults,
|
||||
clientId: this.recurringDepositsAccountTemplate.clientId,
|
||||
mandatoryRecommendedDepositAmount: pt?.minDepositAmount ?? 0,
|
||||
|
||||
+36
-1
@@ -35,6 +35,7 @@
|
||||
<mifosx-recurring-deposits-account-details-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountAndTemplate"
|
||||
(recurringDepositsAccountProductTemplate)="setTemplate($event)"
|
||||
[externalLinkedAccountId]="recurringDepositsAccountMaturityTermsStep?.selectedTransferAccount?.id ?? null"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-details-step>
|
||||
|
||||
@@ -115,7 +116,40 @@
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 4: Preview -->
|
||||
<!-- Tab 4: Terms -->
|
||||
<mat-tab [disabled]="!recurringDepositsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="file-alt" class="cra-tab__icon"></fa-icon>
|
||||
Terms
|
||||
</ng-template>
|
||||
<div class="cra-tab-body">
|
||||
@if (recurringDepositsAccountProductTemplate) {
|
||||
<mifosx-recurring-deposits-account-maturity-terms-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountAndTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[linkedSavingsAccountId]="
|
||||
recurringDepositsAccountDetailsStep?.selectedLinkedAccount?.accountType === 'SAVINGS'
|
||||
? recurringDepositsAccountDetailsStep?.selectedLinkedAccount?.id
|
||||
: null
|
||||
"
|
||||
></mifosx-recurring-deposits-account-maturity-terms-step>
|
||||
}
|
||||
|
||||
<div class="cra-actions">
|
||||
<button mat-stroked-button (click)="prev()">
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<span class="cra-actions__spacer"></span>
|
||||
<button mat-raised-button color="primary" (click)="next()">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Tab 5: Preview -->
|
||||
<mat-tab [disabled]="!recurringDepositsAccountProductTemplate">
|
||||
<ng-template mat-tab-label>
|
||||
<fa-icon icon="eye" class="cra-tab__icon"></fa-icon>
|
||||
@@ -136,6 +170,7 @@
|
||||
[rdDaysInYear]="rdDaysInYear"
|
||||
[rdAppliedRate]="rdAppliedRate"
|
||||
[rdIsSeniorCitizen]="rdIsSeniorCitizen"
|
||||
[closureSettings]="recurringDepositsAccountMaturityTermsStep?.closureSettings ?? {}"
|
||||
[hideButtons]="true"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
|
||||
+10
-1
@@ -25,6 +25,7 @@ import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { RecurringDepositsAccountCurrencyStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-currency-step/recurring-deposits-account-currency-step.component';
|
||||
import { RecurringDepositsAccountInterestRateChartStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-interest-rate-chart-step/recurring-deposits-account-interest-rate-chart-step.component';
|
||||
import { RecurringDepositsAccountPreviewStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-preview-step/recurring-deposits-account-preview-step.component';
|
||||
import { RecurringDepositsAccountMaturityTermsStepComponent } from '../recurring-deposits-account-stepper/recurring-deposits-account-maturity-terms-step/recurring-deposits-account-maturity-terms-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
@@ -41,6 +42,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
RecurringDepositsAccountDetailsStepComponent,
|
||||
RecurringDepositsAccountCurrencyStepComponent,
|
||||
RecurringDepositsAccountInterestRateChartStepComponent,
|
||||
RecurringDepositsAccountMaturityTermsStepComponent,
|
||||
RecurringDepositsAccountPreviewStepComponent
|
||||
]
|
||||
})
|
||||
@@ -63,13 +65,19 @@ export class EditRecurringDepositAccountComponent {
|
||||
@ViewChild(RecurringDepositsAccountInterestRateChartStepComponent)
|
||||
recurringDepositsAccountChartStep: RecurringDepositsAccountInterestRateChartStepComponent;
|
||||
|
||||
@ViewChild(RecurringDepositsAccountPreviewStepComponent)
|
||||
recurringDepositsAccountPreviewStep: RecurringDepositsAccountPreviewStepComponent;
|
||||
|
||||
@ViewChild(RecurringDepositsAccountMaturityTermsStepComponent)
|
||||
recurringDepositsAccountMaturityTermsStep: RecurringDepositsAccountMaturityTermsStepComponent;
|
||||
|
||||
/** Full account data + template from resolver */
|
||||
recurringDepositsAccountAndTemplate: any;
|
||||
/** Product template emitted by details step */
|
||||
recurringDepositsAccountProductTemplate: any;
|
||||
/** Currently visible wizard tab (0-indexed). */
|
||||
selectedTabIndex = 0;
|
||||
private readonly totalTabs = 4;
|
||||
private readonly totalTabs = 5;
|
||||
/** Active CBS base interest rate versions */
|
||||
cbsBaseRates: any[] = [];
|
||||
/** Selected tenure value and frequency from calculator */
|
||||
@@ -277,6 +285,7 @@ export class EditRecurringDepositAccountComponent {
|
||||
...details,
|
||||
...terms,
|
||||
...settings,
|
||||
...(this.recurringDepositsAccountMaturityTermsStep?.closureSettings ?? {}),
|
||||
...productDefaults,
|
||||
clientId: this.recurringDepositsAccountAndTemplate.clientId,
|
||||
mandatoryRecommendedDepositAmount: pt?.minDepositAmount ?? 0,
|
||||
|
||||
+33
-2
@@ -7,7 +7,17 @@
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, Input, Output, EventEmitter, inject, ChangeDetectorRef } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
inject,
|
||||
ChangeDetectorRef
|
||||
} from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, FormsModule } from '@angular/forms';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
@@ -43,7 +53,7 @@ import { IntroducerSearchComponent } from 'app/shared/introducer-search/introduc
|
||||
IntroducerSearchComponent
|
||||
]
|
||||
})
|
||||
export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
export class RecurringDepositsAccountDetailsStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private recurringDepositsService = inject(RecurringDepositsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
@@ -56,6 +66,10 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
/** Recurring Deposits Account Template */
|
||||
@Input() recurringDepositsAccountTemplate: any;
|
||||
|
||||
/** Linked-account id picked elsewhere in the wizard (e.g. the Terms step's Transfer to Savings
|
||||
* account) -- pre-fills this card so the officer doesn't have to pick the same account twice. */
|
||||
@Input() externalLinkedAccountId: number | null = null;
|
||||
|
||||
/** Minimum date allowed. */
|
||||
minDate = new Date(2000, 0, 1);
|
||||
/** Maximum date allowed. */
|
||||
@@ -198,6 +212,23 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
this.dalSearch = '';
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (!changes.externalLinkedAccountId || this.selectedLinkedAccount) {
|
||||
return;
|
||||
}
|
||||
const id = this.externalLinkedAccountId;
|
||||
if (!id) return;
|
||||
const savings = this.dalAllSavings.find((a: any) => a.id === id);
|
||||
const current = this.dalAllCurrent.find((a: any) => a.id === id);
|
||||
if (savings) {
|
||||
this.dalSelectedType = 'SAVINGS';
|
||||
this.selectedLinkedAccount = savings;
|
||||
} else if (current) {
|
||||
this.dalSelectedType = 'CURRENT';
|
||||
this.selectedLinkedAccount = current;
|
||||
}
|
||||
}
|
||||
|
||||
private loadClientAccounts(): void {
|
||||
const clientId = String(this.recurringDepositsAccountTemplate?.clientId);
|
||||
if (!clientId) return;
|
||||
|
||||
+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/.
|
||||
-->
|
||||
|
||||
<form [formGroup]="closureSettingsForm" class="layout-row-wrap gap-2px responsive-column">
|
||||
<h4 class="mat-h4 flex-98">{{ 'labels.heading.For Pre-mature closure' | translate }}</h4>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Maturity Instructions' | translate }}</mat-label>
|
||||
<mat-select formControlName="maturityInstructionId">
|
||||
@for (maturityInstruction of maturityInstructionOptions; track maturityInstruction) {
|
||||
<mat-option [value]="maturityInstruction.id">
|
||||
{{ maturityInstruction.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
@if (closureSettingsForm.contains('transferToSavingsId')) {
|
||||
<div class="dal-card flex-98">
|
||||
<div class="dal-card__header">
|
||||
<div class="dal-card__header-left">
|
||||
<div class="dal-card__icon">
|
||||
<mat-icon>savings</mat-icon>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="dal-card__title">{{ 'labels.inputs.Transfer to savings account' | translate }}</h3>
|
||||
<span class="dal-card__sub">Maturity proceeds will be credited to this account</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dal-card__body">
|
||||
@if (selectedTransferAccount) {
|
||||
<div class="dal-selected">
|
||||
<div class="dal-selected__info">
|
||||
<span class="dal-type-badge dal-type-badge--savings">
|
||||
<mat-icon>savings</mat-icon>
|
||||
Savings
|
||||
</span>
|
||||
<div class="dal-selected__details">
|
||||
<span class="dal-selected__accno">{{ selectedTransferAccount.accountNo }}</span>
|
||||
<span class="dal-selected__product">{{ selectedTransferAccount.savingsProductName }}</span>
|
||||
</div>
|
||||
@if (selectedTransferAccount.summary?.accountBalance != null) {
|
||||
<div class="dal-selected__balance">
|
||||
<span class="dal-selected__bal-label">Balance</span>
|
||||
<span class="dal-selected__bal-amount">
|
||||
{{ selectedTransferAccount.currency?.code }}
|
||||
{{ selectedTransferAccount.summary.accountBalance | number: '1.2-2' }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
type="button"
|
||||
class="dal-change-btn"
|
||||
(click)="clearTransferAccount()"
|
||||
>
|
||||
<mat-icon>swap_horiz</mat-icon>
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<mat-form-field appearance="outline" class="flex-98">
|
||||
<mat-label>{{ 'labels.inputs.Transfer to savings account' | translate }}</mat-label>
|
||||
<mat-select formControlName="transferToSavingsId" required>
|
||||
@for (account of savingsAccountsData; track account) {
|
||||
<mat-option [value]="account.id">
|
||||
({{ account.accountNo }}) {{ account.savingsProductName }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Transfer to savings account is required</mat-error>
|
||||
</mat-form-field>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-checkbox class="flex-98 margin-v" labelPosition="before" formControlName="preClosurePenalApplicable">
|
||||
{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}
|
||||
</mat-checkbox>
|
||||
|
||||
@if (closureSettingsForm.value.preClosurePenalApplicable) {
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Penal Interest' | translate }} (%)</mat-label>
|
||||
<input type="number" matInput formControlName="preClosurePenalInterest" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="flex-48">
|
||||
<mat-label>{{ 'labels.inputs.Period' | translate }}</mat-label>
|
||||
<mat-select formControlName="preClosurePenalInterestOnTypeId">
|
||||
@for (preClosurePenalInterestOnType of preClosurePenalInterestOnTypeData; track preClosurePenalInterestOnType) {
|
||||
<mat-option [value]="preClosurePenalInterestOnType.id">
|
||||
{{ preClosurePenalInterestOnType.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
}
|
||||
</form>
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
// ── Transfer-to-Savings account card (mirrors the Deposit Account Link card) ──
|
||||
|
||||
.dal-card {
|
||||
margin-top: 16px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||||
}
|
||||
|
||||
&__header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 10px;
|
||||
background: #0ea5e9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0c4a6e;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.dal-selected {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 16px 20px;
|
||||
border: 2px solid #0ea5e9;
|
||||
border-radius: 12px;
|
||||
background: #f0f9ff;
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
flex: 1;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&__details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__accno {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0c4a6e;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
&__product {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
&__balance {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__bal-label {
|
||||
font-size: 10px;
|
||||
color: #94a3b8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
&__bal-amount {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #0c4a6e;
|
||||
}
|
||||
}
|
||||
|
||||
.dal-change-btn {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.dal-type-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
background: #f1f5f9;
|
||||
color: #475569;
|
||||
border: 1px solid #e2e8f0;
|
||||
|
||||
mat-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
&--savings {
|
||||
background: #eff6ff;
|
||||
color: #1d4ed8;
|
||||
border-color: #bfdbfe;
|
||||
|
||||
mat-icon {
|
||||
color: #2563eb;
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, Input, OnChanges, SimpleChanges, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, UntypedFormControl, Validators } from '@angular/forms';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
* Recurring Deposit Account Maturity Terms Step -- what happens to this account at maturity
|
||||
* (Withdraw / Transfer to Savings / Reinvest) and the pre-mature-closure penalty, decided
|
||||
* per account rather than silently inherited from the product.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-recurring-deposits-account-maturity-terms-step',
|
||||
templateUrl: './recurring-deposits-account-maturity-terms-step.component.html',
|
||||
styleUrls: ['./recurring-deposits-account-maturity-terms-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatCheckbox,
|
||||
MatIcon
|
||||
]
|
||||
})
|
||||
export class RecurringDepositsAccountMaturityTermsStepComponent implements OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
|
||||
@Input() recurringDepositsAccountTemplate: any;
|
||||
@Input() recurringDepositsAccountProductTemplate: any;
|
||||
/** Account already linked elsewhere in the wizard (the Details step's Deposit Account Link) --
|
||||
* pre-fills Transfer to Savings so the officer doesn't have to pick the same account twice. */
|
||||
@Input() linkedSavingsAccountId: number | null = null;
|
||||
|
||||
closureSettingsForm: UntypedFormGroup;
|
||||
maturityInstructionOptions: any[] = [];
|
||||
savingsAccountsData: any[] = [];
|
||||
preClosurePenalInterestOnTypeData: any[] = [];
|
||||
|
||||
constructor() {
|
||||
this.closureSettingsForm = this.formBuilder.group({
|
||||
maturityInstructionId: [''],
|
||||
preClosurePenalApplicable: [false],
|
||||
preClosurePenalInterest: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
preClosurePenalInterestOnTypeId: ['']
|
||||
});
|
||||
this.closureSettingsForm.get('maturityInstructionId').valueChanges.subscribe((value: number) => {
|
||||
// 200 = Transfer to Savings -- Withdraw Deposit (100) needs no target account, and both
|
||||
// Reinvest options (300/400) open a brand-new RD instead, so they ignore this field too.
|
||||
if (value === 200) {
|
||||
this.closureSettingsForm.addControl(
|
||||
'transferToSavingsId',
|
||||
new UntypedFormControl(this.linkedSavingsAccountId ?? '', Validators.required)
|
||||
);
|
||||
} else {
|
||||
this.closureSettingsForm.removeControl('transferToSavingsId');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.linkedSavingsAccountId) {
|
||||
const control = this.closureSettingsForm.get('transferToSavingsId');
|
||||
if (control && !control.value && this.linkedSavingsAccountId) {
|
||||
control.setValue(this.linkedSavingsAccountId);
|
||||
}
|
||||
}
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
if (template) {
|
||||
// Withdraw Deposit (id 100) books the maturity proceeds out of the bank's cash/vault the moment
|
||||
// the account matures, before the customer has actually collected it -- disabled everywhere.
|
||||
this.maturityInstructionOptions = (template.maturityInstructionOptions ?? []).filter(
|
||||
(option: any) => option.id !== 100
|
||||
);
|
||||
this.savingsAccountsData = template.savingsAccounts ?? [];
|
||||
this.preClosurePenalInterestOnTypeData = template.preClosurePenalInterestOnTypeOptions ?? [];
|
||||
if (!this.closureSettingsForm.get('preClosurePenalApplicable').dirty) {
|
||||
this.closureSettingsForm.patchValue({
|
||||
preClosurePenalApplicable: template.preClosurePenalApplicable ?? false,
|
||||
preClosurePenalInterest: template.preClosurePenalInterest ?? '',
|
||||
preClosurePenalInterestOnTypeId: template.preClosurePenalInterestOnType?.id ?? ''
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** The linked-savings account for the transfer-to-savings maturity instruction, once picked. */
|
||||
get selectedTransferAccount(): any {
|
||||
const id = this.closureSettingsForm.value.transferToSavingsId;
|
||||
if (!id) return null;
|
||||
return this.savingsAccountsData.find((account: any) => account.id === id) ?? null;
|
||||
}
|
||||
|
||||
clearTransferAccount(): void {
|
||||
this.closureSettingsForm.get('transferToSavingsId')?.setValue('');
|
||||
}
|
||||
|
||||
/** Chosen maturity/closure settings, ready to merge into the submit payload. */
|
||||
get closureSettings() {
|
||||
const settings = this.closureSettingsForm.getRawValue();
|
||||
for (const key in settings) {
|
||||
if (settings[key] === '') {
|
||||
delete settings[key];
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
+35
@@ -853,6 +853,41 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-flag-checkered preview-section__icon"></i>
|
||||
<span class="preview-section__title">Maturity & Pre-mature Closure</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Maturity Instructions' | translate }}</span>
|
||||
<span class="preview-value">{{ closureMaturityInstructionLabel }}</span>
|
||||
</div>
|
||||
@if (closureSettings?.transferToSavingsId) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Transfer to savings account' | translate }}</span>
|
||||
<span class="preview-value">{{ closureTransferToSavingsLabel }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}</span>
|
||||
<span class="preview-value">{{ closureSettings?.preClosurePenalApplicable | yesNo }}</span>
|
||||
</div>
|
||||
@if (closureSettings?.preClosurePenalApplicable) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Penal Interest' | translate }} (%)</span>
|
||||
<span class="preview-value">{{ closureSettings?.preClosurePenalInterest }}%</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Period' | translate }}</span>
|
||||
<span class="preview-value">{{ closurePenalInterestOnTypeLabel }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (!hideButtons) {
|
||||
<div class="layout-row responsive-column align-center gap-2px margin-t">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
|
||||
+23
-1
@@ -201,7 +201,29 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
|
||||
/** Expand Chart Slab Index used in the view */
|
||||
expandChartSlabIndex: number;
|
||||
|
||||
constructor() {}
|
||||
/** Maturity/pre-mature-closure choices made in the Terms step -- shown here read-only. */
|
||||
@Input() closureSettings: any = {};
|
||||
|
||||
get closureMaturityInstructionLabel(): string {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
const options = template?.maturityInstructionOptions ?? [];
|
||||
const match = options.find((o: any) => o.id === this.closureSettings?.maturityInstructionId);
|
||||
return match?.value ?? '—';
|
||||
}
|
||||
|
||||
get closureTransferToSavingsLabel(): string {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
const accounts = template?.savingsAccounts ?? [];
|
||||
const match = accounts.find((a: any) => a.id === this.closureSettings?.transferToSavingsId);
|
||||
return match ? `(${match.accountNo}) ${match.savingsProductName}` : '—';
|
||||
}
|
||||
|
||||
get closurePenalInterestOnTypeLabel(): string {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
const options = template?.preClosurePenalInterestOnTypeOptions ?? [];
|
||||
const match = options.find((o: any) => o.id === this.closureSettings?.preClosurePenalInterestOnTypeId);
|
||||
return match?.value ?? '—';
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
|
||||
+19
@@ -17,6 +17,7 @@ import {
|
||||
} from '@angular/forms';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import { OptionData } from 'app/shared/models/option-data.model';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { InputAmountComponent } from '../../../../shared/input-amount/input-amount.component';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
@@ -61,6 +62,10 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
|
||||
periodFrequencyTypeData: any[] = [];
|
||||
/** Preclosure Penal Interest Type on Data */
|
||||
preClosurePenalInterestOnTypeData: any[] = [];
|
||||
/** Options for what happens to the account at maturity */
|
||||
maturityInstructionOptions: OptionData[];
|
||||
/** Savings accounts eligible as a maturity-proceeds transfer target */
|
||||
savingsAccountsData: any;
|
||||
/** Tax Group */
|
||||
taxGroup: any;
|
||||
currency: Currency | null = null;
|
||||
@@ -91,6 +96,7 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
|
||||
: '',
|
||||
maturityInstructions: recurringDepositsAccount.maturityInstructions ?? false,
|
||||
creditMaturityProceeds: recurringDepositsAccount.creditMaturityProceeds ?? false,
|
||||
maturityInstructionId: recurringDepositsAccount.maturityInstructionId ?? '',
|
||||
penaltyGracePeriodMissedInstallments: recurringDepositsAccount.penaltyGracePeriodMissedInstallments ?? false,
|
||||
panelRequestApplicable: recurringDepositsAccount.panelRequestApplicable ?? false,
|
||||
noPenaltyNomineeHeirClosure: recurringDepositsAccount.noPenaltyNomineeHeirClosure ?? false,
|
||||
@@ -182,6 +188,7 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
|
||||
lockinPeriodFrequencyType: [''],
|
||||
maturityInstructions: [false],
|
||||
creditMaturityProceeds: [false],
|
||||
maturityInstructionId: [''],
|
||||
penaltyGracePeriodMissedInstallments: [false],
|
||||
panelRequestApplicable: [false],
|
||||
noPenaltyNomineeHeirClosure: [false],
|
||||
@@ -217,6 +224,8 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
|
||||
this.lockinPeriodFrequencyTypeData = recurringDepositsAccount.lockinPeriodFrequencyTypeOptions;
|
||||
this.periodFrequencyTypeData = recurringDepositsAccount.periodFrequencyTypeOptions;
|
||||
this.preClosurePenalInterestOnTypeData = recurringDepositsAccount.preClosurePenalInterestOnTypeOptions;
|
||||
this.maturityInstructionOptions = recurringDepositsAccount.maturityInstructionOptions;
|
||||
this.savingsAccountsData = recurringDepositsAccount.savingsAccounts;
|
||||
}
|
||||
|
||||
buildDependencies() {
|
||||
@@ -233,6 +242,16 @@ export class RecurringDepositsAccountSettingsStepComponent implements OnInit, On
|
||||
this.recurringDepositAccountSettingsForm.addControl('recurringFrequencyType', new UntypedFormControl(''));
|
||||
}
|
||||
});
|
||||
this.recurringDepositAccountSettingsForm.get('maturityInstructionId').valueChanges.subscribe((value: number) => {
|
||||
if (value > 100) {
|
||||
this.recurringDepositAccountSettingsForm.addControl(
|
||||
'transferToSavingsId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
} else {
|
||||
this.recurringDepositAccountSettingsForm.removeControl('transferToSavingsId');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get recurringDepositAccountSettings() {
|
||||
|
||||
+13
@@ -87,6 +87,19 @@
|
||||
|
||||
<!-- Action menu (kebab) -->
|
||||
<div class="rd-hero__actions">
|
||||
@if (
|
||||
!recurringDepositsAccountData.status.rejected &&
|
||||
!recurringDepositsAccountData.status.submittedAndPendingApproval
|
||||
) {
|
||||
<button
|
||||
mat-icon-button
|
||||
matTooltip="Print Certificate"
|
||||
(click)="printCertificate()"
|
||||
aria-label="Print certificate"
|
||||
>
|
||||
<fa-icon icon="print"></fa-icon>
|
||||
</button>
|
||||
}
|
||||
<button mat-icon-button [matMenuTriggerFor]="accountMenu" aria-label="Account actions" yPosition="below">
|
||||
<fa-icon icon="ellipsis-v"></fa-icon>
|
||||
</button>
|
||||
|
||||
+15
@@ -22,6 +22,7 @@ import { RecurringDepositsButtonsConfiguration } from './recurring-deposits-butt
|
||||
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 { DepositCertificateDialogComponent } from 'app/shared/deposit-certificate-dialog/deposit-certificate-dialog.component';
|
||||
import { Currency } from 'app/shared/models/general.model';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { AccountsService } from 'app/accounts/accounts.service';
|
||||
@@ -426,6 +427,20 @@ export class RecurringDepositsAccountViewComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a printable deposit certificate for this account.
|
||||
*/
|
||||
printCertificate() {
|
||||
this.dialog.open(DepositCertificateDialogComponent, {
|
||||
width: '900px',
|
||||
data: {
|
||||
accountType: 'RD',
|
||||
accountData: this.recurringDepositsAccountData,
|
||||
clientId: this.recurringDepositsAccountData.clientId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables withhold tax for recurring deposits account
|
||||
* Recurring deposits endpoint is not supported so using Savings endpoint.
|
||||
|
||||
Reference in New Issue
Block a user