feat: update FD product and account creation UI to match RD layout
- Matched all FD product stepper steps (Details, Currency, Terms, Settings, Charges, Accounting) to RD product UI - Updated FD account creation stepper structure with conditional steps and Previous/Next navigation
This commit is contained in:
+85
-35
@@ -34,7 +34,8 @@
|
||||
<fa-icon icon="eye" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositAccountDetailsForm">
|
||||
<!-- Step 1: Details -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-account-details-step
|
||||
@@ -44,48 +45,97 @@
|
||||
</mifosx-fixed-deposit-account-details-step>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositAccountTermsForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
||||
@if (fixedDepositsAccountProductTemplate) {
|
||||
<!-- Step 2: Terms -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-account-terms-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-terms-step>
|
||||
</mat-step>
|
||||
<mifosx-fixed-deposit-account-terms-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-terms-step>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositAccountSettingsForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<mifosx-fixed-deposit-account-settings-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-settings-step>
|
||||
</mat-step>
|
||||
<!-- Step 3: Settings -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
|
||||
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.INTEREST RATE CHART' | translate }}</ng-template>
|
||||
<mifosx-fixed-deposit-account-settings-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-settings-step>
|
||||
|
||||
<mifosx-fixed-deposit-account-interest-rate-chart-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-interest-rate-chart-step>
|
||||
</mat-step>
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||
<!-- Step 4: Interest Rate Chart -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.INTEREST RATE CHART' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-account-charges-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
[fixedDepositAccountFormValid]="fixedDepositAccountFormValid"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-charges-step>
|
||||
</mat-step>
|
||||
<mifosx-fixed-deposit-account-interest-rate-chart-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-interest-rate-chart-step>
|
||||
|
||||
@if (fixedDepositAccountFormValid) {
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 5: Charges -->
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-account-charges-step
|
||||
[fixedDepositsAccountTemplate]="fixedDepositsAccountTemplate"
|
||||
[fixedDepositsAccountProductTemplate]="fixedDepositsAccountProductTemplate"
|
||||
[fixedDepositAccountFormValid]="fixedDepositAccountFormValid"
|
||||
>
|
||||
</mifosx-fixed-deposit-account-charges-step>
|
||||
|
||||
<div class="layout-row align-center gap-2percent margin-t responsive-column">
|
||||
<button mat-raised-button matStepperPrevious>
|
||||
<fa-icon icon="arrow-left" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button matStepperNext>
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<fa-icon icon="arrow-right" class="m-l-10"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-step>
|
||||
|
||||
<!-- Step 6: Preview -->
|
||||
<mat-step state="preview" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||
<mifosx-fixed-deposit-account-preview-step
|
||||
|
||||
+13
-8
@@ -20,7 +20,14 @@ import { FixedDepositAccountTermsStepComponent } from '../fixed-deposit-account-
|
||||
import { FixedDepositAccountSettingsStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-settings-step/fixed-deposit-account-settings-step.component';
|
||||
import { FixedDepositAccountChargesStepComponent } from '../fixed-deposit-account-stepper/fixed-deposit-account-charges-step/fixed-deposit-account-charges-step.component';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
|
||||
import {
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious
|
||||
} from '@angular/material/stepper';
|
||||
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';
|
||||
@@ -37,6 +44,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatStepperNext,
|
||||
MatStepperPrevious,
|
||||
FaIconComponent,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
@@ -55,17 +64,13 @@ export class CreateFixedDepositAccountComponent {
|
||||
private fixedDepositsService = inject(FixedDepositsService);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
/** Fixed Deposits Account Details Step */
|
||||
@ViewChild(FixedDepositAccountDetailsStepComponent, { static: true })
|
||||
fixedDepositsAccountDetailsStep: FixedDepositAccountDetailsStepComponent;
|
||||
/** Fixed Deposits Account Terms Step */
|
||||
@ViewChild(FixedDepositAccountTermsStepComponent, { static: true })
|
||||
@ViewChild(FixedDepositAccountTermsStepComponent)
|
||||
fixedDepositAccountTermsStep: FixedDepositAccountTermsStepComponent;
|
||||
/** Fixed Deposits Account Settings Step */
|
||||
@ViewChild(FixedDepositAccountSettingsStepComponent, { static: true })
|
||||
@ViewChild(FixedDepositAccountSettingsStepComponent)
|
||||
fixedDepositAccountSettingsStep: FixedDepositAccountSettingsStepComponent;
|
||||
/** Fixed Deposits Account Charges Step */
|
||||
@ViewChild(FixedDepositAccountChargesStepComponent, { static: true })
|
||||
@ViewChild(FixedDepositAccountChargesStepComponent)
|
||||
fixedDepositAccountChargesStep: FixedDepositAccountChargesStepComponent;
|
||||
|
||||
/** Fixed Deposits Account Template */
|
||||
|
||||
+53
-5
@@ -67,12 +67,60 @@
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-chart-area fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.INTEREST RATE CHART' | translate }}
|
||||
CBS Base Chart
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-interest-rate-chart-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
></mifosx-fixed-deposit-product-interest-rate-chart-step>
|
||||
@if (activeBaseVersion) {
|
||||
<div class="base-rate-card">
|
||||
<div class="base-rate-header">
|
||||
<div>
|
||||
<h3 class="base-rate-title">{{ activeBaseVersion.versionLabel }}</h3>
|
||||
<p class="base-rate-meta">
|
||||
Active CBS base chart
|
||||
<span>Effective from {{ activeBaseVersion.effectiveFromDate }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<span class="base-rate-badge">Active</span>
|
||||
</div>
|
||||
|
||||
@if (activeBaseVersion.remarks) {
|
||||
<p class="base-rate-remarks">{{ activeBaseVersion.remarks }}</p>
|
||||
}
|
||||
|
||||
<table class="base-rate-table" mat-table [dataSource]="activeBaseVersion.slabs">
|
||||
<ng-container matColumnDef="srNo">
|
||||
<th mat-header-cell *matHeaderCellDef>Sr.</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.srNo }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>Particular</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.description }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fromPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>From</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.fromPeriod }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="toPeriod">
|
||||
<th mat-header-cell *matHeaderCellDef>To</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.toPeriod ?? 'N/A' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="periodTypeLabel">
|
||||
<th mat-header-cell *matHeaderCellDef>Period Type</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="annualInterestRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Annual Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let slab">
|
||||
<strong>{{ slab.annualInterestRate | number: '1.2-2' }}</strong>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="fdSlabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: fdSlabColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
} @else {
|
||||
<p>Loading CBS base chart...</p>
|
||||
}
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -112,7 +160,7 @@
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-preview-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[chartSlabsDisplayedColumns]="fixedDepositProductInterestRateChartStep.chartSlabsDisplayedColumns"
|
||||
[chartSlabsDisplayedColumns]="fdSlabColumns"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[fixedDepositProduct]="fixedDepositProduct"
|
||||
(submitEvent)="submit()"
|
||||
|
||||
+48
@@ -71,6 +71,54 @@ $primary: #1353a0;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.base-rate-card {
|
||||
padding: 16px;
|
||||
border: 1px solid #dbe4f0;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, #f8fbff 0%, #fff 100%);
|
||||
}
|
||||
|
||||
.base-rate-header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.base-rate-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.base-rate-meta {
|
||||
margin: 6px 0 0;
|
||||
color: #5b6b7f;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.base-rate-badge {
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.base-rate-remarks {
|
||||
margin: 0 0 12px;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.base-rate-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.fdp-action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
+51
-21
@@ -15,9 +15,20 @@ import { FixedDepositProductDetailsStepComponent } from '../fixed-deposit-produc
|
||||
import { FixedDepositProductCurrencyStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-currency-step/fixed-deposit-product-currency-step.component';
|
||||
import { FixedDepositProductTermsStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component';
|
||||
import { FixedDepositProductSettingsStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-settings-step/fixed-deposit-product-settings-step.component';
|
||||
import { FixedDepositProductInterestRateChartStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-interest-rate-chart-step/fixed-deposit-product-interest-rate-chart-step.component';
|
||||
import { FixedDepositProductChargesStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-charges-step/fixed-deposit-product-charges-step.component';
|
||||
import { FixedDepositProductAccountingStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-accounting-step/fixed-deposit-product-accounting-step.component';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
|
||||
/** Custom Services */
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
@@ -40,10 +51,19 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
FixedDepositProductCurrencyStepComponent,
|
||||
FixedDepositProductTermsStepComponent,
|
||||
FixedDepositProductSettingsStepComponent,
|
||||
FixedDepositProductInterestRateChartStepComponent,
|
||||
FixedDepositProductChargesStepComponent,
|
||||
FixedDepositProductAccountingStepComponent,
|
||||
FixedDepositProductPreviewStepComponent
|
||||
FixedDepositProductPreviewStepComponent,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
]
|
||||
})
|
||||
export class CreateFixedDepositProductComponent {
|
||||
@@ -61,8 +81,6 @@ export class CreateFixedDepositProductComponent {
|
||||
fixedDepositProductTermsStep: FixedDepositProductTermsStepComponent;
|
||||
@ViewChild(FixedDepositProductSettingsStepComponent, { static: true })
|
||||
fixedDepositProductSettingsStep: FixedDepositProductSettingsStepComponent;
|
||||
@ViewChild(FixedDepositProductInterestRateChartStepComponent, { static: true })
|
||||
fixedDepositProductInterestRateChartStep: FixedDepositProductInterestRateChartStepComponent;
|
||||
@ViewChild(FixedDepositProductChargesStepComponent, { static: true })
|
||||
fixedDepositProductChargesStep: FixedDepositProductChargesStepComponent;
|
||||
@ViewChild(FixedDepositProductAccountingStepComponent, { static: true })
|
||||
@@ -71,19 +89,29 @@ export class CreateFixedDepositProductComponent {
|
||||
fixedDepositProductsTemplate: any;
|
||||
accountingRuleData: string[] = [];
|
||||
selectedTabIndex = 0;
|
||||
|
||||
/**
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
* @param {ProductsService} productsService Products Service.
|
||||
* @param {Router} router Router for navigation.
|
||||
* @param {SettingsService} settingsService Settings Service.
|
||||
*/
|
||||
fdInterestRates: any[] = [];
|
||||
readonly fdSlabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
'fromPeriod',
|
||||
'toPeriod',
|
||||
'periodTypeLabel',
|
||||
'annualInterestRate'
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { fixedDepositProductsTemplate: any }) => {
|
||||
this.fixedDepositProductsTemplate = data.fixedDepositProductsTemplate;
|
||||
});
|
||||
this.accountingRuleData = this.accounting.getAccountingRulesForSavings();
|
||||
this.productsService.getRdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => {
|
||||
this.fdInterestRates = rates || [];
|
||||
},
|
||||
error: () => {
|
||||
this.fdInterestRates = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get fixedDepositProductDetailsForm() {
|
||||
@@ -102,10 +130,6 @@ export class CreateFixedDepositProductComponent {
|
||||
return this.fixedDepositProductSettingsStep.fixedDepositProductSettingsForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductInterestRateChartForm() {
|
||||
return this.fixedDepositProductInterestRateChartStep.fixedDepositProductInterestRateChartForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductAccountingForm() {
|
||||
return this.fixedDepositProductAccountingStep.fixedDepositProductAccountingForm;
|
||||
}
|
||||
@@ -120,29 +144,35 @@ export class CreateFixedDepositProductComponent {
|
||||
this.fixedDepositProductCurrencyForm.valid &&
|
||||
this.fixedDepositProductTermsForm.valid &&
|
||||
this.fixedDepositProductSettingsForm.valid &&
|
||||
this.fixedDepositProductInterestRateChartForm.valid &&
|
||||
this.fixedDepositProductAccountingForm.valid
|
||||
);
|
||||
}
|
||||
|
||||
get activeBaseVersion() {
|
||||
return this.fdInterestRates.find((v: any) => v.isActive) ?? this.fdInterestRates[0] ?? null;
|
||||
}
|
||||
|
||||
get activeBaseSlabs() {
|
||||
return this.activeBaseVersion?.slabs || [];
|
||||
}
|
||||
|
||||
get fixedDepositProduct() {
|
||||
return {
|
||||
...this.fixedDepositProductDetailsStep.fixedDepositProductDetails,
|
||||
...this.fixedDepositProductCurrencyStep.fixedDepositProductCurrency,
|
||||
...this.fixedDepositProductTermsStep.fixedDepositProductTerms,
|
||||
...this.fixedDepositProductSettingsStep.fixedDepositProductSettings,
|
||||
...this.fixedDepositProductInterestRateChartStep.fixedDepositProductInterestRateChart,
|
||||
...this.fixedDepositProductChargesStep.fixedDepositProductCharges,
|
||||
...this.fixedDepositProductAccountingStep.fixedDepositProductAccounting
|
||||
...this.fixedDepositProductAccountingStep.fixedDepositProductAccounting,
|
||||
fdSlabs: this.activeBaseSlabs
|
||||
};
|
||||
}
|
||||
|
||||
submit() {
|
||||
// TODO: Update once language and date settings are setup
|
||||
const fixedDepositProduct = {
|
||||
...this.fixedDepositProduct,
|
||||
charges: this.fixedDepositProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code // locale required for depositAmount
|
||||
locale: this.settingsService.language.code
|
||||
};
|
||||
delete fixedDepositProduct.advancedAccountingRules;
|
||||
this.productsService.createFixedDepositProduct(fixedDepositProduct).subscribe((response: any) => {
|
||||
|
||||
+93
-292
@@ -9,310 +9,111 @@
|
||||
<form [formGroup]="fixedDepositProductAccountingForm">
|
||||
<div class="fdpa-section">
|
||||
<div class="fdpa-section__header">
|
||||
<i class="fas fa-book"></i>
|
||||
<h3>{{ 'labels.heading.Accounting' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpa-grid">
|
||||
<div class="fdpa-field fdpa-field--full">
|
||||
<mat-radio-group class="fdpa-radio-group" formControlName="accountingRule">
|
||||
@for (accountingRule of accountingRuleData; track accountingRule; let i = $index) {
|
||||
<mat-radio-button [value]="i + 1">
|
||||
{{ 'labels.accounting.' + accountingRule | translate }}
|
||||
</mat-radio-button>
|
||||
}
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isCashOrAccrualAccounting()) {
|
||||
<div class="fdpa-section">
|
||||
<div class="fdpa-section__header">
|
||||
<i class="fas fa-landmark"></i>
|
||||
<h3>{{ 'labels.heading.Assets' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpa-grid">
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.savingsReferenceAccountId"
|
||||
[glAccountList]="assetAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Saving reference'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
@if (isAccrualAccounting()) {
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.feesReceivableAccountId"
|
||||
[glAccountList]="assetAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Fees Receivable'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
}
|
||||
@if (isAccrualAccounting()) {
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.penaltiesReceivableAccountId"
|
||||
[glAccountList]="assetAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Penalties Receivable'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
}
|
||||
</div>
|
||||
<span class="fdpa-section__icon">
|
||||
<i class="fas fa-book"></i>
|
||||
</span>
|
||||
<h3>Accounting</h3>
|
||||
</div>
|
||||
|
||||
<div class="fdpa-section">
|
||||
<div class="fdpa-section__header">
|
||||
<i class="fas fa-balance-scale"></i>
|
||||
<h3>{{ 'labels.heading.Liabilities' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpa-grid">
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.savingsControlAccountId"
|
||||
[glAccountList]="liabilityAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Saving control'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.transfersInSuspenseAccountId"
|
||||
[glAccountList]="liabilityAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Savings transfers in suspense'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
@if (isAccrualAccounting()) {
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.interestPayableAccountId"
|
||||
[glAccountList]="liabilityAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Interest Payable'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdpa-section">
|
||||
<div class="fdpa-section__header">
|
||||
<i class="fas fa-arrow-circle-down"></i>
|
||||
<h3>{{ 'labels.heading.Expenses' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpa-grid">
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.interestOnSavingsAccountId"
|
||||
[glAccountList]="expenseAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Interest on savings'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdpa-section">
|
||||
<div class="fdpa-section__header">
|
||||
<i class="fas fa-arrow-circle-up"></i>
|
||||
<h3>{{ 'labels.heading.Income' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpa-grid">
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.incomeFromFeeAccountId"
|
||||
[glAccountList]="incomeAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Income from fees'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-field"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.controls.incomeFromPenaltyAccountId"
|
||||
[glAccountList]="incomeAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Income from penalties'"
|
||||
>
|
||||
</mifosx-gl-account-selector>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdpa-section">
|
||||
<div class="fdpa-section__header">
|
||||
<i class="fas fa-cogs"></i>
|
||||
<h3>{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpa-grid">
|
||||
<div class="fdpa-field fdpa-field--full fdpa-checkbox-row">
|
||||
<mat-checkbox formControlName="advancedAccountingRules"></mat-checkbox>
|
||||
<div class="fdpa-section__body">
|
||||
<div class="fdpa-group">
|
||||
<div class="fdpa-group__header">
|
||||
<h4>Accounting Rule</h4>
|
||||
</div>
|
||||
<div class="fdpa-radio-group">
|
||||
<mat-radio-button [value]="2" [checked]="true" [disabled]="true">Cash</mat-radio-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductAccountingForm.value.advancedAccountingRules) {
|
||||
<!-- Payment Fund Source -->
|
||||
<div class="fdpa-subsection">
|
||||
<div class="fdpa-subsection__header">
|
||||
<h4>{{ 'labels.heading.Configure Fund Sources for Payment Channels' | translate }}</h4>
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="fdpa-add-btn"
|
||||
(click)="add('PaymentFundSource', paymentChannelToFundSourceMappings)"
|
||||
>
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
@if (paymentChannelToFundSourceMappings.value.length !== 0) {
|
||||
<div class="fdpa-table-wrapper">
|
||||
<table mat-table [dataSource]="paymentChannelToFundSourceMappings.value">
|
||||
<ng-container matColumnDef="paymentTypeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.heading.Payment Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let paymentFundSource">
|
||||
{{ paymentFundSource.paymentTypeId | find: paymentTypeData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fundSourceAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fund Source' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let paymentFundSource">
|
||||
{{ paymentFundSource.fundSourceAccountId | find: assetAccountData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.buttons.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let paymentFundSource; let i = index">
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
(click)="edit('PaymentFundSource', paymentChannelToFundSourceMappings, i)"
|
||||
>
|
||||
<fa-icon icon="edit"></fa-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="delete(paymentChannelToFundSourceMappings, i)">
|
||||
<fa-icon icon="trash"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (existCharges()) {
|
||||
<!-- Fees Income -->
|
||||
<div class="fdpa-subsection">
|
||||
<div class="fdpa-subsection__header">
|
||||
<h4>{{ 'labels.heading.Map Fees to Specific Income Accounts' | translate }}</h4>
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="fdpa-add-btn"
|
||||
(click)="add('FeesIncome', feeToIncomeAccountMappings)"
|
||||
>
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
@if (isCashOrAccrualAccounting()) {
|
||||
@for (section of accountingSectionConfigs; track section.key) {
|
||||
<div class="fdpa-group">
|
||||
<div class="fdpa-group__header">
|
||||
<h4>{{ section.title }}</h4>
|
||||
<button type="button" class="fdpa-add-btn" (click)="addOtherGlMapping(section.otherMappingsControlName)">
|
||||
+ Add Other
|
||||
</button>
|
||||
</div>
|
||||
@if (feeToIncomeAccountMappings.value.length !== 0) {
|
||||
<div class="fdpa-table-wrapper">
|
||||
<table mat-table [dataSource]="feeToIncomeAccountMappings.value">
|
||||
<ng-container matColumnDef="chargeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.catalogs.Fees' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let feesIncome">
|
||||
{{ feesIncome.chargeId | find: chargeData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incomeAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let feesIncome">
|
||||
{{ feesIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.buttons.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let feesIncome; let i = index">
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
(click)="edit('FeesIncome', feeToIncomeAccountMappings, i)"
|
||||
<div class="fdpa-list">
|
||||
@for (row of section.rows; track row.key) {
|
||||
@if (!row.accrualOnly || isAccrualAccounting()) {
|
||||
<div class="fdpa-row">
|
||||
<div class="fdpa-row__meta">
|
||||
<span
|
||||
class="fdpa-pill"
|
||||
[class.fdpa-pill--green]="section.pillClass === 'green'"
|
||||
[class.fdpa-pill--amber]="section.pillClass === 'amber'"
|
||||
[class.fdpa-pill--blue]="section.pillClass === 'blue'"
|
||||
>
|
||||
<fa-icon icon="edit"></fa-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="delete(feeToIncomeAccountMappings, i)">
|
||||
<fa-icon icon="trash"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Penalty Income -->
|
||||
<div class="fdpa-subsection">
|
||||
<div class="fdpa-subsection__header">
|
||||
<h4>
|
||||
{{ 'labels.catalogs.Penalties' | translate }}
|
||||
{{ 'labels.heading.Map Penalties to Specific Income Accounts' | translate }}
|
||||
</h4>
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="fdpa-add-btn"
|
||||
(click)="add('PenaltyIncome', penaltyToIncomeAccountMappings)"
|
||||
>
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
</button>
|
||||
{{ section.pillLabel }}
|
||||
</span>
|
||||
<span class="fdpa-row__label">{{ row.label }}</span>
|
||||
</div>
|
||||
<mifosx-gl-account-selector
|
||||
class="fdpa-row__input"
|
||||
[inputFormControl]="fixedDepositProductAccountingForm.get(row.controlName)"
|
||||
[glAccountList]="row.options"
|
||||
[required]="row.required"
|
||||
[inputLabel]="row.inputLabel"
|
||||
></mifosx-gl-account-selector>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@if (penaltyToIncomeAccountMappings.value.length !== 0) {
|
||||
<div class="fdpa-table-wrapper">
|
||||
<table mat-table [dataSource]="penaltyToIncomeAccountMappings.value">
|
||||
<ng-container matColumnDef="chargeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let penaltyIncome">
|
||||
{{ penaltyIncome.chargeId | find: penaltyData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incomeAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let penaltyIncome">
|
||||
{{ penaltyIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.buttons.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let penaltyIncome; let i = index">
|
||||
<button
|
||||
mat-icon-button
|
||||
color="primary"
|
||||
(click)="edit('PenaltyIncome', penaltyToIncomeAccountMappings, i)"
|
||||
|
||||
<div class="fdpa-list" [formArrayName]="section.otherMappingsControlName">
|
||||
@for (
|
||||
otherRow of getOtherGlMappingsArray(section.otherMappingsControlName).controls;
|
||||
track $index;
|
||||
let i = $index
|
||||
) {
|
||||
<div class="fdpa-other-row-wrap">
|
||||
<div class="fdpa-row fdpa-row--other" [formGroupName]="i">
|
||||
<div class="fdpa-row__meta fdpa-row__meta--other">
|
||||
<span
|
||||
class="fdpa-pill"
|
||||
[class.fdpa-pill--green]="section.pillClass === 'green'"
|
||||
[class.fdpa-pill--amber]="section.pillClass === 'amber'"
|
||||
[class.fdpa-pill--blue]="section.pillClass === 'blue'"
|
||||
>
|
||||
<fa-icon icon="edit"></fa-icon>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="delete(penaltyToIncomeAccountMappings, i)">
|
||||
<fa-icon icon="trash"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
{{ section.pillLabel }}
|
||||
</span>
|
||||
<input
|
||||
class="fdpa-row__name fdpa-row__name--other-account"
|
||||
formControlName="accountName"
|
||||
placeholder="Enter account name..."
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="fdpa-row__actions fdpa-row__actions--other">
|
||||
<mat-form-field class="fdpa-row__other-field" appearance="outline" subscriptSizing="dynamic">
|
||||
<mat-label>GL Code</mat-label>
|
||||
<mat-select
|
||||
[formControl]="getOtherGlMappingControl(section.otherMappingsControlName, i, 'glAccountId')"
|
||||
>
|
||||
@for (glAccount of section.otherAccountOptions; track glAccount.id) {
|
||||
<mat-option [value]="glAccount.id">
|
||||
{{ '(' + glAccount.glCode + ') ' + glAccount.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="fdpa-remove-btn fdpa-remove-btn--side"
|
||||
(click)="removeOtherGlMapping(section.otherMappingsControlName, i)"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+267
-40
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-700: #334155;
|
||||
$gray-900: #202124;
|
||||
|
||||
.fdpa-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -15,45 +21,244 @@
|
||||
& + & {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-section__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-section__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.fdpa-section__body {
|
||||
padding: 16px 24px 20px;
|
||||
}
|
||||
|
||||
.fdpa-group {
|
||||
& + & {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
}
|
||||
h4 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
.fdpa-add-btn {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #2563eb;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.fdpa-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.fdpa-list + .fdpa-list {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.fdpa-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
|
||||
@media (width <= 900px) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-row:has(mifosx-gl-account-selector) {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.fdpa-row--other {
|
||||
border-style: dashed;
|
||||
border-color: #cbd5e1;
|
||||
background: #f8fafc;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
|
||||
@media (width <= 1100px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-field {
|
||||
.fdpa-other-row-wrap {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
@media (width <= 1100px) {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-row__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.fdpa-row__meta--other {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.fdpa-row__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
.fdpa-row__input {
|
||||
flex: 0 0 22rem;
|
||||
max-width: 22rem;
|
||||
min-width: 18rem;
|
||||
|
||||
@media (width <= 900px) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-row__input :is(mifosx-gl-account-selector) {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.fdpa-row__input :is(mifosx-gl-account-selector) :is(.layout-column) {
|
||||
width: 100%;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.fdpa-row__input :is(mifosx-gl-account-selector) :is(mat-form-field) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fdpa-row__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fdpa-row__actions--other {
|
||||
justify-content: flex-end;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
@media (width <= 1100px) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-row__name {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: 24rem;
|
||||
height: 40px;
|
||||
border: 1px solid #cbd5e1;
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
background: #fff;
|
||||
|
||||
@media (width <= 900px) {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-row__name--other-account {
|
||||
max-width: 100%;
|
||||
flex: 1 1 auto;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.fdpa-row__other-field {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.fdpa-row__other-field :is(mat-form-field) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fdpa-row__other-field :is(.mat-mdc-form-field-required-marker) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.fdpa-remove-btn {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #ef4444;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.fdpa-remove-btn--side {
|
||||
place-self: center end;
|
||||
margin-right: 4px;
|
||||
|
||||
@media (width <= 1100px) {
|
||||
justify-self: start;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,23 +266,34 @@
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (width <= 600px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-checkbox-row {
|
||||
display: flex;
|
||||
.fdpa-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
justify-content: center;
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
|
||||
.fdpa-add-btn {
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
&--green {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
&--amber {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
&--blue {
|
||||
background: #dbeafe;
|
||||
color: #2563eb;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-subsection {
|
||||
@@ -95,13 +311,13 @@
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fdpa-table-wrapper {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -109,3 +325,14 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 900px) {
|
||||
.fdpa-row__meta {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.fdpa-row__input {
|
||||
flex: 1 1 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
+330
-373
@@ -8,434 +8,391 @@
|
||||
|
||||
import { Component, Input, OnInit, inject } from '@angular/core';
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
UntypedFormArray,
|
||||
UntypedFormBuilder,
|
||||
UntypedFormControl,
|
||||
UntypedFormGroup,
|
||||
Validators,
|
||||
ReactiveFormsModule
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
|
||||
import { GlAccountSelectorComponent } from 'app/shared/accounting/gl-account-selector/gl-account-selector.component';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
|
||||
import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
|
||||
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
||||
import { GlAccountSelectorComponent } from '../../../../shared/accounting/gl-account-selector/gl-account-selector.component';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatButton, MatIconButton } from '@angular/material/button';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { FindPipe } from '../../../../pipes/find.pipe';
|
||||
import { GLAccount } from 'app/shared/models/general.model';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income';
|
||||
|
||||
type AccountingRowConfig = {
|
||||
key: string;
|
||||
label: string;
|
||||
inputLabel: string;
|
||||
controlName: string;
|
||||
options: GLAccount[];
|
||||
required?: boolean;
|
||||
accrualOnly?: boolean;
|
||||
};
|
||||
|
||||
type AccountingSectionConfig = {
|
||||
key: AccountingSectionKey;
|
||||
title: string;
|
||||
pillLabel: string;
|
||||
pillClass: 'green' | 'amber' | 'blue';
|
||||
rows: AccountingRowConfig[];
|
||||
otherMappingsControlName: string;
|
||||
otherAccountOptions: GLAccount[];
|
||||
};
|
||||
|
||||
type OtherGlMappingValue = {
|
||||
accountName: string;
|
||||
glAccountId: number | null;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-fixed-deposit-product-accounting-step',
|
||||
templateUrl: './fixed-deposit-product-accounting-step.component.html',
|
||||
styleUrls: ['./fixed-deposit-product-accounting-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatRadioGroup,
|
||||
MatRadioButton,
|
||||
GlAccountSelectorComponent,
|
||||
MatCheckbox,
|
||||
FaIconComponent,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatIconButton,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
FindPipe
|
||||
GlAccountSelectorComponent
|
||||
]
|
||||
})
|
||||
export class FixedDepositProductAccountingStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
dialog = inject(MatDialog);
|
||||
private accounting = inject(Accounting);
|
||||
private translateService = inject(TranslateService);
|
||||
|
||||
@Input() fixedDepositProductsTemplate: any;
|
||||
@Input() accountingRuleData: any;
|
||||
@Input() fixedDepositProductFormValid: boolean;
|
||||
|
||||
fixedDepositProductAccountingForm: UntypedFormGroup;
|
||||
|
||||
chargeData: any;
|
||||
penaltyData: any;
|
||||
paymentTypeData: any;
|
||||
assetAccountData: any;
|
||||
incomeAccountData: any;
|
||||
expenseAccountData: any;
|
||||
liabilityAccountData: any;
|
||||
|
||||
paymentFundSourceDisplayedColumns: string[] = [
|
||||
'paymentTypeId',
|
||||
'fundSourceAccountId',
|
||||
'actions'
|
||||
];
|
||||
feesPenaltyIncomeDisplayedColumns: string[] = [
|
||||
'chargeId',
|
||||
'incomeAccountId',
|
||||
'actions'
|
||||
];
|
||||
assetAccountOptions: GLAccount[] = [];
|
||||
liabilityAccountOptions: GLAccount[] = [];
|
||||
expenseAccountOptions: GLAccount[] = [];
|
||||
incomeAccountOptions: GLAccount[] = [];
|
||||
accountingSectionConfigs: AccountingSectionConfig[] = [];
|
||||
|
||||
constructor() {
|
||||
this.createfixedDepositProductAccountingForm();
|
||||
this.createFixedDepositProductAccountingForm();
|
||||
this.setConditionalControls();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.chargeData = this.fixedDepositProductsTemplate.chargeOptions || [];
|
||||
this.penaltyData = this.fixedDepositProductsTemplate.penaltyOptions || [];
|
||||
this.paymentTypeData = this.fixedDepositProductsTemplate.paymentTypeOptions || [];
|
||||
this.assetAccountData = this.fixedDepositProductsTemplate.accountingMappingOptions.assetAccountOptions || [];
|
||||
this.incomeAccountData = this.fixedDepositProductsTemplate.accountingMappingOptions.incomeAccountOptions || [];
|
||||
this.expenseAccountData = this.fixedDepositProductsTemplate.accountingMappingOptions.expenseAccountOptions || [];
|
||||
this.liabilityAccountData =
|
||||
this.fixedDepositProductsTemplate.accountingMappingOptions.liabilityAccountOptions || [];
|
||||
if (!(this.fixedDepositProductsTemplate === undefined) && this.fixedDepositProductsTemplate.id) {
|
||||
this.assignAccountingStepData();
|
||||
}
|
||||
const accountingMappings = this.fixedDepositProductsTemplate?.accountingMappings || {};
|
||||
const mappings = {
|
||||
...accountingMappings,
|
||||
otherGlMappingsMap: this.fixedDepositProductsTemplate?.otherGlMappingsMap
|
||||
};
|
||||
const accountingMappingOptions = this.fixedDepositProductsTemplate?.accountingMappingOptions || {};
|
||||
const accountingRuleId = 2; // Always Cash
|
||||
|
||||
this.assetAccountOptions = accountingMappingOptions.assetAccountOptions || [];
|
||||
this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || [];
|
||||
this.expenseAccountOptions = accountingMappingOptions.expenseAccountOptions || [];
|
||||
this.incomeAccountOptions = accountingMappingOptions.incomeAccountOptions || [];
|
||||
|
||||
this.buildAccountingSectionConfigs();
|
||||
|
||||
this.fixedDepositProductAccountingForm.patchValue({ accountingRule: accountingRuleId });
|
||||
this.syncAccountingControls(accountingRuleId);
|
||||
this.patchAccountingValues(mappings);
|
||||
}
|
||||
|
||||
assignAccountingStepData() {
|
||||
this.fixedDepositProductAccountingForm.patchValue({
|
||||
accountingRule: this.fixedDepositProductsTemplate.accountingRule.id
|
||||
});
|
||||
if (this.isCashOrAccrualAccounting()) {
|
||||
this.fixedDepositProductAccountingForm.patchValue({
|
||||
savingsReferenceAccountId: this.fixedDepositProductsTemplate.accountingMappings.savingsReferenceAccount.id,
|
||||
savingsControlAccountId: this.fixedDepositProductsTemplate.accountingMappings.savingsControlAccount.id,
|
||||
transfersInSuspenseAccountId:
|
||||
this.fixedDepositProductsTemplate.accountingMappings.transfersInSuspenseAccount.id,
|
||||
incomeFromFeeAccountId: this.fixedDepositProductsTemplate.accountingMappings.incomeFromFeeAccount.id,
|
||||
incomeFromPenaltyAccountId: this.fixedDepositProductsTemplate.accountingMappings.incomeFromPenaltyAccount.id,
|
||||
interestOnSavingsAccountId: this.fixedDepositProductsTemplate.accountingMappings.interestOnSavingsAccount.id
|
||||
});
|
||||
|
||||
if (this.isAccrualAccounting()) {
|
||||
this.fixedDepositProductAccountingForm.patchValue({
|
||||
feesReceivableAccountId: this.fixedDepositProductsTemplate.accountingMappings.feeReceivableAccount.id,
|
||||
penaltiesReceivableAccountId:
|
||||
this.fixedDepositProductsTemplate.accountingMappings.penaltyReceivableAccount.id,
|
||||
interestPayableAccountId: this.fixedDepositProductsTemplate.accountingMappings.interestPayableAccount.id
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
this.fixedDepositProductsTemplate.paymentChannelToFundSourceMappings ||
|
||||
this.fixedDepositProductsTemplate.feeToIncomeAccountMappings ||
|
||||
this.fixedDepositProductsTemplate.penaltyToIncomeAccountMappings
|
||||
) {
|
||||
this.fixedDepositProductAccountingForm.patchValue({
|
||||
advancedAccountingRules: true
|
||||
});
|
||||
}
|
||||
if (this.fixedDepositProductsTemplate.paymentChannelToFundSourceMappings) {
|
||||
this.fixedDepositProductsTemplate.paymentChannelToFundSourceMappings.forEach(
|
||||
(paymentChannelToFundSourceMapping: any) => {
|
||||
const paymentChannelToFundSourceMappingData = this.formBuilder.group({
|
||||
paymentTypeId: [
|
||||
paymentChannelToFundSourceMapping.paymentType.id,
|
||||
Validators.required
|
||||
],
|
||||
fundSourceAccountId: [
|
||||
paymentChannelToFundSourceMapping.fundSourceAccount.id,
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
const formArray = this.fixedDepositProductAccountingForm.controls[
|
||||
'paymentChannelToFundSourceMappings'
|
||||
] as UntypedFormArray;
|
||||
formArray.push(paymentChannelToFundSourceMappingData);
|
||||
}
|
||||
);
|
||||
}
|
||||
if (this.fixedDepositProductsTemplate.feeToIncomeAccountMappings) {
|
||||
this.fixedDepositProductsTemplate.feeToIncomeAccountMappings.forEach((feeToIncomeAccountMapping: any) => {
|
||||
const feeToIncomeAccountMappingData = this.formBuilder.group({
|
||||
chargeId: [
|
||||
feeToIncomeAccountMapping.charge.id,
|
||||
Validators.required
|
||||
],
|
||||
incomeAccountId: [
|
||||
feeToIncomeAccountMapping.incomeAccount.id,
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
const formArray = this.fixedDepositProductAccountingForm.controls[
|
||||
'feeToIncomeAccountMappings'
|
||||
] as UntypedFormArray;
|
||||
formArray.push(feeToIncomeAccountMappingData);
|
||||
});
|
||||
}
|
||||
if (this.fixedDepositProductsTemplate.penaltyToIncomeAccountMappings) {
|
||||
this.fixedDepositProductsTemplate.penaltyToIncomeAccountMappings.forEach(
|
||||
(penaltyToIncomeAccountMapping: any) => {
|
||||
const penaltyToIncomeAccountMappingData = this.formBuilder.group({
|
||||
chargeId: [
|
||||
penaltyToIncomeAccountMapping.charge.id,
|
||||
Validators.required
|
||||
],
|
||||
incomeAccountId: [
|
||||
penaltyToIncomeAccountMapping.incomeAccount.id,
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
const formArray = this.fixedDepositProductAccountingForm.controls[
|
||||
'penaltyToIncomeAccountMappings'
|
||||
] as UntypedFormArray;
|
||||
formArray.push(penaltyToIncomeAccountMappingData);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createfixedDepositProductAccountingForm() {
|
||||
createFixedDepositProductAccountingForm() {
|
||||
this.fixedDepositProductAccountingForm = this.formBuilder.group({
|
||||
accountingRule: [1]
|
||||
accountingRule: [2],
|
||||
assetsOtherGlMappings: this.formBuilder.array([]),
|
||||
liabilitiesOtherGlMappings: this.formBuilder.array([]),
|
||||
expensesOtherGlMappings: this.formBuilder.array([]),
|
||||
incomeOtherGlMappings: this.formBuilder.array([])
|
||||
});
|
||||
}
|
||||
|
||||
existCharges(): boolean {
|
||||
return this.chargeData.length > 0;
|
||||
}
|
||||
|
||||
setConditionalControls() {
|
||||
this.fixedDepositProductAccountingForm.get('accountingRule').valueChanges.subscribe((accountingRule: any) => {
|
||||
if (accountingRule === 2 || accountingRule === 3) {
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'savingsReferenceAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'savingsControlAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'transfersInSuspenseAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'interestOnSavingsAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'incomeFromFeeAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'incomeFromPenaltyAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl('advancedAccountingRules', new UntypedFormControl(false));
|
||||
|
||||
if (accountingRule === 3) {
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'feesReceivableAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'penaltiesReceivableAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'interestPayableAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
}
|
||||
|
||||
this.fixedDepositProductAccountingForm
|
||||
.get('advancedAccountingRules')
|
||||
.valueChanges.subscribe((advancedAccountingRules: boolean) => {
|
||||
if (advancedAccountingRules) {
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'paymentChannelToFundSourceMappings',
|
||||
this.formBuilder.array([])
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'feeToIncomeAccountMappings',
|
||||
this.formBuilder.array([])
|
||||
);
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
'penaltyToIncomeAccountMappings',
|
||||
this.formBuilder.array([])
|
||||
);
|
||||
} else {
|
||||
this.fixedDepositProductAccountingForm.removeControl('paymentChannelToFundSourceMappings');
|
||||
this.fixedDepositProductAccountingForm.removeControl('feeToIncomeAccountMappings');
|
||||
this.fixedDepositProductAccountingForm.removeControl('penaltyToIncomeAccountMappings');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.fixedDepositProductAccountingForm.removeControl('savingsReferenceAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('overdraftPortfolioControlId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('savingsControlAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('transfersInSuspenseAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('interestOnSavingsAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('writeOffAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('incomeFromFeeAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('incomeFromPenaltyAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('incomeFromInterestId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('advancedAccountingRules');
|
||||
this.fixedDepositProductAccountingForm.removeControl('escheatLiabilityId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('feesReceivableAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('penaltiesReceivableAccountId');
|
||||
this.fixedDepositProductAccountingForm.removeControl('interestPayableAccountId');
|
||||
}
|
||||
private setConditionalControls() {
|
||||
this.fixedDepositProductAccountingForm.get('accountingRule')?.valueChanges.subscribe((rule: number) => {
|
||||
this.syncAccountingControls(rule);
|
||||
});
|
||||
}
|
||||
|
||||
get paymentChannelToFundSourceMappings(): UntypedFormArray {
|
||||
return this.fixedDepositProductAccountingForm.get('paymentChannelToFundSourceMappings') as UntypedFormArray;
|
||||
}
|
||||
|
||||
get feeToIncomeAccountMappings(): UntypedFormArray {
|
||||
return this.fixedDepositProductAccountingForm.get('feeToIncomeAccountMappings') as UntypedFormArray;
|
||||
}
|
||||
|
||||
get penaltyToIncomeAccountMappings(): UntypedFormArray {
|
||||
return this.fixedDepositProductAccountingForm.get('penaltyToIncomeAccountMappings') as UntypedFormArray;
|
||||
}
|
||||
|
||||
add(formType: string, formArray: UntypedFormArray) {
|
||||
const data = { ...this.getData(formType), pristine: false };
|
||||
const dialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
dialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
formArray.push(response.data);
|
||||
}
|
||||
private patchAccountingValues(mappings: any) {
|
||||
this.fixedDepositProductAccountingForm.patchValue({
|
||||
savingsReferenceAccountId: mappings.savingsReferenceAccount?.id || '',
|
||||
savingsControlAccountId: mappings.savingsControlAccount?.id || '',
|
||||
transfersInSuspenseAccountId: mappings.transfersInSuspenseAccount?.id || '',
|
||||
interestOnSavingsAccountId: mappings.interestOnSavingsAccount?.id || '',
|
||||
incomeFromFeeAccountId: mappings.incomeFromFeeAccount?.id || '',
|
||||
incomeFromPenaltyAccountId: mappings.incomeFromPenaltyAccount?.id || '',
|
||||
feesReceivableAccountId: mappings.feeReceivableAccount?.id || '',
|
||||
penaltiesReceivableAccountId: mappings.penaltyReceivableAccount?.id || '',
|
||||
interestPayableAccountId: mappings.interestPayableAccount?.id || ''
|
||||
});
|
||||
}
|
||||
|
||||
edit(formType: string, formArray: UntypedFormArray, index: number) {
|
||||
const data = { ...this.getData(formType, formArray.at(index).value), layout: { addButtonText: 'Edit' } };
|
||||
const dialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
dialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
formArray.at(index).patchValue(response.data.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delete(formArray: UntypedFormArray, index: number) {
|
||||
const dialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: { deleteContext: this.translateService.instant('labels.text.this') }
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.delete) {
|
||||
formArray.removeAt(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getData(formType: string, values?: any) {
|
||||
switch (formType) {
|
||||
case 'PaymentFundSource':
|
||||
return {
|
||||
title: 'Configure Fund Sources for Payment Channels',
|
||||
formfields: this.getPaymentFundSourceFormfields(values)
|
||||
};
|
||||
case 'FeesIncome':
|
||||
return { title: 'Map Fees to Income Accounts', formfields: this.getFeesIncomeFormfields(values) };
|
||||
case 'PenaltyIncome':
|
||||
return {
|
||||
title: 'Map Penalties to Specific Income Accounts',
|
||||
formfields: this.getPenaltyIncomeFormfields(values)
|
||||
};
|
||||
if (this.isCashOrAccrualAccounting()) {
|
||||
const glMappings = mappings.otherGlMappingsMap || mappings.otherGlMappings;
|
||||
this.setOtherGlMappings('assetsOtherGlMappings', glMappings?.assets || [], this.assetAccountOptions);
|
||||
this.setOtherGlMappings(
|
||||
'liabilitiesOtherGlMappings',
|
||||
glMappings?.liabilities || [],
|
||||
this.liabilityAccountOptions
|
||||
);
|
||||
this.setOtherGlMappings('expensesOtherGlMappings', glMappings?.expenses || [], this.expenseAccountOptions);
|
||||
this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions);
|
||||
}
|
||||
}
|
||||
|
||||
getPaymentFundSourceFormfields(values?: any) {
|
||||
const formfields: FormfieldBase[] = [
|
||||
new SelectBase({
|
||||
controlName: 'paymentTypeId',
|
||||
label: 'Payment Type',
|
||||
value: values ? values.paymentTypeId : this.paymentTypeData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.paymentTypeData },
|
||||
required: true,
|
||||
order: 1
|
||||
}),
|
||||
new SelectBase({
|
||||
controlName: 'fundSourceAccountId',
|
||||
label: 'Fund Source',
|
||||
value: values ? values.fundSourceAccountId : this.assetAccountData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.assetAccountData },
|
||||
required: true,
|
||||
order: 2
|
||||
})
|
||||
];
|
||||
return formfields;
|
||||
private createOtherGlMappingGroup(otherGlMapping: Partial<OtherGlMappingValue> = {}) {
|
||||
return this.formBuilder.group({
|
||||
accountName: [
|
||||
otherGlMapping.accountName || '',
|
||||
Validators.required
|
||||
],
|
||||
glAccountId: [
|
||||
otherGlMapping.glAccountId || '',
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
getFeesIncomeFormfields(values?: any) {
|
||||
const formfields: FormfieldBase[] = [
|
||||
new SelectBase({
|
||||
controlName: 'chargeId',
|
||||
label: 'Fees',
|
||||
value: values ? values.chargeId : this.chargeData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.chargeData },
|
||||
required: true,
|
||||
order: 1
|
||||
}),
|
||||
new SelectBase({
|
||||
controlName: 'incomeAccountId',
|
||||
label: 'Income Account',
|
||||
value: values ? values.incomeAccountId : this.incomeAccountData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.incomeAccountData },
|
||||
required: true,
|
||||
order: 2
|
||||
})
|
||||
];
|
||||
return formfields;
|
||||
getOtherGlMappingsArray(controlName: string): UntypedFormArray {
|
||||
return this.fixedDepositProductAccountingForm.get(controlName) as UntypedFormArray;
|
||||
}
|
||||
|
||||
getPenaltyIncomeFormfields(values?: any) {
|
||||
const formfields: FormfieldBase[] = [
|
||||
new SelectBase({
|
||||
controlName: 'chargeId',
|
||||
label: 'Penalty',
|
||||
value: values ? values.chargeId : this.penaltyData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.penaltyData },
|
||||
required: true,
|
||||
order: 1
|
||||
}),
|
||||
new SelectBase({
|
||||
controlName: 'incomeAccountId',
|
||||
label: 'Income Account',
|
||||
value: values ? values.incomeAccountId : this.incomeAccountData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.incomeAccountData },
|
||||
required: true,
|
||||
order: 2
|
||||
})
|
||||
private setOtherGlMappings(controlName: string, otherGlMappings: any[], options: GLAccount[]) {
|
||||
const arr = this.getOtherGlMappingsArray(controlName);
|
||||
arr.clear();
|
||||
(otherGlMappings || []).forEach((m) => {
|
||||
arr.push(
|
||||
this.createOtherGlMappingGroup({
|
||||
accountName: m.accountName,
|
||||
glAccountId: m.glAccountId || options.find((o) => o.glCode === m.glCode)?.id || null
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
addOtherGlMapping(controlName: string) {
|
||||
this.getOtherGlMappingsArray(controlName).push(this.createOtherGlMappingGroup());
|
||||
}
|
||||
|
||||
removeOtherGlMapping(controlName: string, index: number) {
|
||||
this.getOtherGlMappingsArray(controlName).removeAt(index);
|
||||
}
|
||||
|
||||
getOtherGlMappingControl(controlName: string, index: number, fieldName: string): UntypedFormControl {
|
||||
return this.getOtherGlMappingsArray(controlName).at(index).get(fieldName) as UntypedFormControl;
|
||||
}
|
||||
|
||||
private clearOtherGlMappings() {
|
||||
[
|
||||
'assetsOtherGlMappings',
|
||||
'liabilitiesOtherGlMappings',
|
||||
'expensesOtherGlMappings',
|
||||
'incomeOtherGlMappings'
|
||||
].forEach((c) => this.getOtherGlMappingsArray(c).clear());
|
||||
}
|
||||
|
||||
private getOtherGlMappings() {
|
||||
const map = (controlName: string, options: GLAccount[]) =>
|
||||
this.getOtherGlMappingsArray(controlName)
|
||||
.value.filter((item: OtherGlMappingValue) => !!item.accountName || !!item.glAccountId)
|
||||
.map((item: OtherGlMappingValue) => ({
|
||||
accountName: item.accountName,
|
||||
glCode: options.find((o) => o.id === item.glAccountId)?.glCode || ''
|
||||
}))
|
||||
.filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode);
|
||||
|
||||
const otherGlMappings = {
|
||||
assets: map('assetsOtherGlMappings', this.assetAccountOptions),
|
||||
liabilities: map('liabilitiesOtherGlMappings', this.liabilityAccountOptions),
|
||||
expenses: map('expensesOtherGlMappings', this.expenseAccountOptions),
|
||||
income: map('incomeOtherGlMappings', this.incomeAccountOptions)
|
||||
};
|
||||
return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null;
|
||||
}
|
||||
|
||||
private syncAccountingControls(accountingRule: number) {
|
||||
const addControl = (name: string, required = false) => {
|
||||
if (!this.fixedDepositProductAccountingForm.contains(name)) {
|
||||
this.fixedDepositProductAccountingForm.addControl(
|
||||
name,
|
||||
new UntypedFormControl('', required ? Validators.required : undefined)
|
||||
);
|
||||
} else if (required) {
|
||||
this.fixedDepositProductAccountingForm.get(name)?.setValidators(Validators.required);
|
||||
this.fixedDepositProductAccountingForm.get(name)?.updateValueAndValidity({ emitEvent: false });
|
||||
}
|
||||
};
|
||||
const removeControl = (name: string) => {
|
||||
if (this.fixedDepositProductAccountingForm.contains(name)) {
|
||||
this.fixedDepositProductAccountingForm.removeControl(name);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.accounting.isCashOrAccrualAccountingRuleId(accountingRule)) {
|
||||
addControl('savingsReferenceAccountId', true);
|
||||
addControl('savingsControlAccountId', true);
|
||||
addControl('transfersInSuspenseAccountId', true);
|
||||
addControl('interestOnSavingsAccountId', true);
|
||||
addControl('incomeFromFeeAccountId', true);
|
||||
addControl('incomeFromPenaltyAccountId', true);
|
||||
|
||||
if (this.accounting.isAccrualAccountingRuleId(accountingRule)) {
|
||||
addControl('feesReceivableAccountId', true);
|
||||
addControl('penaltiesReceivableAccountId', true);
|
||||
addControl('interestPayableAccountId', true);
|
||||
} else {
|
||||
removeControl('feesReceivableAccountId');
|
||||
removeControl('penaltiesReceivableAccountId');
|
||||
removeControl('interestPayableAccountId');
|
||||
}
|
||||
} else {
|
||||
[
|
||||
'savingsReferenceAccountId',
|
||||
'savingsControlAccountId',
|
||||
'transfersInSuspenseAccountId',
|
||||
'interestOnSavingsAccountId',
|
||||
'incomeFromFeeAccountId',
|
||||
'incomeFromPenaltyAccountId',
|
||||
'feesReceivableAccountId',
|
||||
'penaltiesReceivableAccountId',
|
||||
'interestPayableAccountId'
|
||||
].forEach(removeControl);
|
||||
this.clearOtherGlMappings();
|
||||
}
|
||||
}
|
||||
|
||||
buildAccountingSectionConfigs() {
|
||||
this.accountingSectionConfigs = [
|
||||
{
|
||||
key: 'assets',
|
||||
title: 'Assets',
|
||||
pillLabel: 'Asset',
|
||||
pillClass: 'green',
|
||||
rows: [
|
||||
{
|
||||
key: 'savingsReferenceAccountId',
|
||||
label: 'Savings Reference Account',
|
||||
inputLabel: 'Savings reference account',
|
||||
controlName: 'savingsReferenceAccountId',
|
||||
options: this.assetAccountOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'feesReceivableAccountId',
|
||||
label: 'Fees Receivable Account',
|
||||
inputLabel: 'Fees receivable account',
|
||||
controlName: 'feesReceivableAccountId',
|
||||
options: this.assetAccountOptions,
|
||||
accrualOnly: true
|
||||
},
|
||||
{
|
||||
key: 'penaltiesReceivableAccountId',
|
||||
label: 'Penalties Receivable Account',
|
||||
inputLabel: 'Penalties receivable account',
|
||||
controlName: 'penaltiesReceivableAccountId',
|
||||
options: this.assetAccountOptions,
|
||||
accrualOnly: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'assetsOtherGlMappings',
|
||||
otherAccountOptions: this.assetAccountOptions
|
||||
},
|
||||
{
|
||||
key: 'liabilities',
|
||||
title: 'Liabilities',
|
||||
pillLabel: 'Liability',
|
||||
pillClass: 'green',
|
||||
rows: [
|
||||
{
|
||||
key: 'savingsControlAccountId',
|
||||
label: 'Savings Control Account',
|
||||
inputLabel: 'Savings control account',
|
||||
controlName: 'savingsControlAccountId',
|
||||
options: this.liabilityAccountOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'transfersInSuspenseAccountId',
|
||||
label: 'Transfers In Suspense Account',
|
||||
inputLabel: 'Transfers in suspense account',
|
||||
controlName: 'transfersInSuspenseAccountId',
|
||||
options: this.liabilityAccountOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'interestPayableAccountId',
|
||||
label: 'Interest Payable Account',
|
||||
inputLabel: 'Interest payable account',
|
||||
controlName: 'interestPayableAccountId',
|
||||
options: this.liabilityAccountOptions,
|
||||
accrualOnly: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'liabilitiesOtherGlMappings',
|
||||
otherAccountOptions: this.liabilityAccountOptions
|
||||
},
|
||||
{
|
||||
key: 'expenses',
|
||||
title: 'Expenses',
|
||||
pillLabel: 'Expense',
|
||||
pillClass: 'amber',
|
||||
rows: [
|
||||
{
|
||||
key: 'interestOnSavingsAccountId',
|
||||
label: 'Interest On Savings Account',
|
||||
inputLabel: 'Interest on savings account',
|
||||
controlName: 'interestOnSavingsAccountId',
|
||||
options: this.expenseAccountOptions,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'expensesOtherGlMappings',
|
||||
otherAccountOptions: this.expenseAccountOptions
|
||||
},
|
||||
{
|
||||
key: 'income',
|
||||
title: 'Income',
|
||||
pillLabel: 'Income',
|
||||
pillClass: 'blue',
|
||||
rows: [
|
||||
{
|
||||
key: 'incomeFromFeeAccountId',
|
||||
label: 'Income From Fees',
|
||||
inputLabel: 'Income from fees',
|
||||
controlName: 'incomeFromFeeAccountId',
|
||||
options: this.incomeAccountOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'incomeFromPenaltyAccountId',
|
||||
label: 'Income From Penalties',
|
||||
inputLabel: 'Income from penalties',
|
||||
controlName: 'incomeFromPenaltyAccountId',
|
||||
options: this.incomeAccountOptions,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'incomeOtherGlMappings',
|
||||
otherAccountOptions: this.incomeAccountOptions
|
||||
}
|
||||
];
|
||||
return formfields;
|
||||
}
|
||||
|
||||
get fixedDepositProductAccounting() {
|
||||
return this.fixedDepositProductAccountingForm.value;
|
||||
const {
|
||||
assetsOtherGlMappings,
|
||||
liabilitiesOtherGlMappings,
|
||||
expensesOtherGlMappings,
|
||||
incomeOtherGlMappings,
|
||||
...accounting
|
||||
} = this.fixedDepositProductAccountingForm.value;
|
||||
const otherGlMappings = this.getOtherGlMappings();
|
||||
return { ...accounting, ...(otherGlMappings ? { otherGlMappings } : {}) };
|
||||
}
|
||||
|
||||
isCashOrAccrualAccounting(): boolean {
|
||||
|
||||
+60
-61
@@ -8,22 +8,21 @@
|
||||
|
||||
<div class="fdpch-section">
|
||||
<div class="fdpch-section__header">
|
||||
<i class="fas fa-money-check-alt"></i>
|
||||
<h3>{{ 'labels.inputs.Charge' | translate }}</h3>
|
||||
<i class="fa fa-file-invoice-dollar"></i>
|
||||
<h3>{{ 'labels.heading.Charges' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpch-grid">
|
||||
<mat-form-field appearance="outline" class="fdpch-field">
|
||||
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
|
||||
<mat-select #charge>
|
||||
@for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) {
|
||||
<mat-option [value]="charge">
|
||||
{{ charge.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="fdpch-field fdpch-btn-wrap">
|
||||
<div class="fdpch-section__body">
|
||||
<div class="fdpch-add-row">
|
||||
<mat-form-field appearance="outline" class="fdpch-add-row__select">
|
||||
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
|
||||
<mat-select #charge>
|
||||
@for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) {
|
||||
<mat-option [value]="charge">
|
||||
{{ charge.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
@@ -36,51 +35,51 @@
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (chargesDataSource.length > 0) {
|
||||
<div class="fdpch-table-wrapper">
|
||||
<table mat-table [dataSource]="chargesDataSource">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="chargeCalculationType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeCalculationType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.amount | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="chargeTimeType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeTimeType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="action">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
|
||||
<fa-icon icon="trash"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (chargesDataSource.length > 0) {
|
||||
<div class="fdpch-table-wrapper">
|
||||
<table mat-table [dataSource]="chargesDataSource">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="chargeCalculationType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeCalculationType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.amount | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="chargeTimeType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeTimeType.value }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="action">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
|
||||
<fa-icon icon="trash"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
+11
-23
@@ -12,10 +12,6 @@
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -36,30 +32,22 @@
|
||||
color: #202124;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fdpch-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
&__body {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpch-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpch-btn-wrap {
|
||||
.fdpch-add-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__select {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpch-add-btn {
|
||||
@@ -73,7 +61,7 @@
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin: 0 24px 16px;
|
||||
margin-top: 12px;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
+90
-48
@@ -10,60 +10,102 @@
|
||||
<div class="fdpc-section">
|
||||
<div class="fdpc-section__header">
|
||||
<i class="fas fa-coins"></i>
|
||||
<h3>{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||
<span>Currency & Account Status Configuration</span>
|
||||
</div>
|
||||
<div class="fdpc-grid">
|
||||
<mat-form-field appearance="outline" class="fdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
||||
<mat-select formControlName="currencyCode" required>
|
||||
@for (currency of currencyData; track currency) {
|
||||
<mat-option [value]="currency.code">
|
||||
{{ currency.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
matTooltip="{{ 'tooltips.Decimal places to be used to track and report' | translate }}"
|
||||
formControlName="digitsAfterDecimal"
|
||||
required
|
||||
/>
|
||||
<mat-error *ngIf="fixedDepositProductCurrencyForm.get('digitsAfterDecimal')?.hasError('required')">
|
||||
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
<mat-error *ngIf="fixedDepositProductCurrencyForm.get('digitsAfterDecimal')?.hasError('min')">
|
||||
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.Minimum Value must be' | translate }}
|
||||
<strong>0</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="fdpc-field fdpc-field--full fdpc-checkbox-row">
|
||||
<mat-checkbox labelPosition="before" formControlName="setMultiples">
|
||||
{{ 'labels.inputs.Set the fixed deposit installment in multiples of' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductCurrencyForm.value.setMultiples) {
|
||||
<div class="fdpc-body">
|
||||
<div class="fdpc-grid">
|
||||
<mat-form-field appearance="outline" class="fdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Currency in multiples of' | translate }}</mat-label>
|
||||
<input type="number" min="1" matInput formControlName="inMultiplesOf" />
|
||||
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
||||
<mat-select
|
||||
formControlName="currencyCode"
|
||||
matTooltip="{{ 'tooltips.The currency in which the deposit will be disbursed' | translate }}"
|
||||
required
|
||||
>
|
||||
@for (currency of currencyData; track currency) {
|
||||
<mat-option [value]="currency.code">
|
||||
{{ currency.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-hint>Default: Indian Rupee - auto-set for domestic products</mat-hint>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Currency in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
||||
<mat-select
|
||||
formControlName="digitsAfterDecimal"
|
||||
matTooltip="{{ 'tooltips.Decimal places to be used to track and report' | translate }}"
|
||||
required
|
||||
>
|
||||
@for (places of decimalPlacesData; track places) {
|
||||
<mat-option [value]="places">{{ places }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="fdpc-alert">
|
||||
<div class="fdpc-alert__content">
|
||||
<h4>Dormancy Alert</h4>
|
||||
<p>Enable automated notifications when an account becomes inactive based on product rules.</p>
|
||||
</div>
|
||||
<mat-radio-group class="fdpc-alert__choices" formControlName="dormancyAlert">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="fdpc-alert">
|
||||
<div class="fdpc-alert__content">
|
||||
<h4>Balance Swap Option</h4>
|
||||
<p>Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.</p>
|
||||
</div>
|
||||
<mat-radio-group class="fdpc-alert__choices" formControlName="balanceSwapOption">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="fdpc-alert">
|
||||
<div class="fdpc-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Micro, Small and Medium Enterprises</p>
|
||||
</div>
|
||||
<mat-radio-group class="fdpc-alert__choices" formControlName="msme">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="fdpc-alert">
|
||||
<div class="fdpc-alert__content">
|
||||
<h4>Nomination Facility</h4>
|
||||
<p>Allow account holders to designate individuals to receive funds in the event of account holder's death.</p>
|
||||
</div>
|
||||
<mat-radio-group class="fdpc-alert__choices" formControlName="nominationFacility">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="fdpc-alert">
|
||||
<div class="fdpc-alert__content">
|
||||
<h4>Auto Debit Facility</h4>
|
||||
<p>Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.</p>
|
||||
</div>
|
||||
<mat-radio-group class="fdpc-alert__choices" formControlName="autoDebitFacility">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+51
-12
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.fdpc-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -21,23 +27,27 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
span {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fdpc-body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.fdpc-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@@ -51,14 +61,43 @@
|
||||
|
||||
.fdpc-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpc-checkbox-row {
|
||||
.fdpc-alert {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 18px 24px 24px;
|
||||
border-top: 1px solid $border;
|
||||
background: #f8fafc;
|
||||
|
||||
@media (width <= 760px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__content {
|
||||
h4 {
|
||||
margin: 0 0 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #64748b;
|
||||
max-width: 540px;
|
||||
}
|
||||
}
|
||||
|
||||
&__choices {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
+33
-44
@@ -6,12 +6,10 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input, inject, DestroyRef } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { startWith } from 'rxjs/operators';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
@@ -21,18 +19,23 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTooltip,
|
||||
MatCheckbox
|
||||
MatRadioGroup,
|
||||
MatRadioButton
|
||||
]
|
||||
})
|
||||
export class FixedDepositProductCurrencyStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@Input() fixedDepositProductsTemplate: any;
|
||||
|
||||
fixedDepositProductCurrencyForm: UntypedFormGroup;
|
||||
|
||||
currencyData: any;
|
||||
decimalPlacesData = [
|
||||
2,
|
||||
0,
|
||||
3
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createFixedDepositProductCurrencyForm();
|
||||
@@ -42,13 +45,17 @@ export class FixedDepositProductCurrencyStepComponent implements OnInit {
|
||||
this.currencyData = this.fixedDepositProductsTemplate.currencyOptions;
|
||||
|
||||
this.fixedDepositProductCurrencyForm.patchValue({
|
||||
currencyCode: this.fixedDepositProductsTemplate.currency?.code || this.currencyData[0].code,
|
||||
digitsAfterDecimal: this.fixedDepositProductsTemplate.digitsAfterDecimal ?? '',
|
||||
setMultiples: !!this.fixedDepositProductsTemplate.inMultiplesOf,
|
||||
inMultiplesOf: this.fixedDepositProductsTemplate.inMultiplesOf ?? ''
|
||||
currencyCode: this.fixedDepositProductsTemplate.currency?.code || this.currencyData?.[0]?.code || '',
|
||||
digitsAfterDecimal:
|
||||
this.fixedDepositProductsTemplate.digitsAfterDecimal ??
|
||||
this.fixedDepositProductsTemplate.currency?.decimalPlaces ??
|
||||
2,
|
||||
dormancyAlert: this.fixedDepositProductsTemplate.dormancyAlert ?? false,
|
||||
balanceSwapOption: this.fixedDepositProductsTemplate.balanceSwapOption ?? false,
|
||||
msme: this.fixedDepositProductsTemplate.msme ?? false,
|
||||
nominationFacility: this.fixedDepositProductsTemplate.nominationFacility ?? false,
|
||||
autoDebitFacility: this.fixedDepositProductsTemplate.autoDebitFacility ?? false
|
||||
});
|
||||
|
||||
this.setupConditionalValidation();
|
||||
}
|
||||
|
||||
createFixedDepositProductCurrencyForm() {
|
||||
@@ -58,48 +65,30 @@ export class FixedDepositProductCurrencyStepComponent implements OnInit {
|
||||
Validators.required
|
||||
],
|
||||
digitsAfterDecimal: [
|
||||
'',
|
||||
2,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
setMultiples: [false],
|
||||
inMultiplesOf: ['']
|
||||
dormancyAlert: [false],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
nominationFacility: [false],
|
||||
autoDebitFacility: [false]
|
||||
});
|
||||
}
|
||||
|
||||
setupConditionalValidation() {
|
||||
const inMultiplesOfControl = this.fixedDepositProductCurrencyForm.get('inMultiplesOf');
|
||||
const setMultiplesControl = this.fixedDepositProductCurrencyForm.get('setMultiples');
|
||||
|
||||
setMultiplesControl?.valueChanges
|
||||
.pipe(startWith(setMultiplesControl.value), takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe((checked) => {
|
||||
if (checked) {
|
||||
inMultiplesOfControl?.setValidators([
|
||||
Validators.required,
|
||||
Validators.min(1)
|
||||
]);
|
||||
} else {
|
||||
inMultiplesOfControl?.clearValidators();
|
||||
inMultiplesOfControl?.setValue('');
|
||||
}
|
||||
inMultiplesOfControl?.updateValueAndValidity();
|
||||
});
|
||||
}
|
||||
|
||||
get fixedDepositProductCurrency() {
|
||||
const formValue = this.fixedDepositProductCurrencyForm.value;
|
||||
const result: any = {
|
||||
return {
|
||||
currencyCode: formValue.currencyCode,
|
||||
digitsAfterDecimal: formValue.digitsAfterDecimal
|
||||
digitsAfterDecimal: formValue.digitsAfterDecimal,
|
||||
dormancyAlert: formValue.dormancyAlert,
|
||||
balanceSwapOption: formValue.balanceSwapOption,
|
||||
msme: formValue.msme,
|
||||
nominationFacility: formValue.nominationFacility,
|
||||
autoDebitFacility: formValue.autoDebitFacility
|
||||
};
|
||||
|
||||
if (formValue.inMultiplesOf !== '' && formValue.inMultiplesOf !== null && formValue.inMultiplesOf !== undefined) {
|
||||
result.inMultiplesOf = formValue.inMultiplesOf;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+87
-10
@@ -9,13 +9,18 @@
|
||||
<form [formGroup]="fixedDepositProductDetailsForm">
|
||||
<div class="fdpd-section">
|
||||
<div class="fdpd-section__header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<h3>{{ 'labels.inputs.Product Name' | translate }} & {{ 'labels.inputs.Description' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpd-grid">
|
||||
<mat-form-field appearance="outline" class="fdpd-field">
|
||||
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" required />
|
||||
<input
|
||||
matInput
|
||||
formControlName="name"
|
||||
matTooltip="{{ 'tooltips.Product name is a unique identifier' | translate }}"
|
||||
required
|
||||
/>
|
||||
@if (fixedDepositProductDetailsForm.controls.name.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
@@ -26,7 +31,13 @@
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpd-field">
|
||||
<mat-label>{{ 'labels.inputs.Short Name' | translate }}</mat-label>
|
||||
<input matInput formControlName="shortName" maxlength="4" required />
|
||||
<input
|
||||
matInput
|
||||
formControlName="shortName"
|
||||
maxlength="4"
|
||||
matTooltip="{{ 'tooltips.Short name is a unique identifier' | translate }}"
|
||||
required
|
||||
/>
|
||||
@if (fixedDepositProductDetailsForm.controls.shortName.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Short Name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
@@ -37,14 +48,80 @@
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpd-field fdpd-field--full">
|
||||
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
|
||||
<textarea matInput formControlName="description" required></textarea>
|
||||
@if (fixedDepositProductDetailsForm.controls.description.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Description' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
<textarea
|
||||
matInput
|
||||
formControlName="description"
|
||||
matTooltip="{{
|
||||
'tooltips.Description is used to provide additional information regarding the purpose' | translate
|
||||
}}"
|
||||
cdkTextareaAutosize
|
||||
cdkAutosizeMinRows="2"
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdpd-section">
|
||||
<div class="fdpd-section__header">
|
||||
<i class="fa fa-tags"></i>
|
||||
<h3>Product Classification</h3>
|
||||
</div>
|
||||
|
||||
<div class="fdpd-grid fdpd-grid--classification">
|
||||
<mat-form-field appearance="outline" class="fdpd-field">
|
||||
<mat-label>Account Type</mat-label>
|
||||
<mat-select formControlName="accountType" required>
|
||||
@for (accountType of accountTypeData; track accountType) {
|
||||
<mat-option [value]="accountType">{{ accountType }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Account Type <strong>is required</strong></mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpd-field">
|
||||
<mat-label>Customer Segment</mat-label>
|
||||
<mat-select formControlName="customerSegment">
|
||||
@for (customerSegment of customerSegmentData; track customerSegment) {
|
||||
<mat-option [value]="customerSegment">{{ customerSegment }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpd-field">
|
||||
<mat-label>GL Account (Liability)</mat-label>
|
||||
<mat-select formControlName="glAccountId" [compareWith]="compareById">
|
||||
@for (account of liabilityAccountOptions; track account.id) {
|
||||
<mat-option [value]="account.id">{{ account.name }} ({{ account.glCode }})</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpd-field" (click)="effectiveToPicker.open()">
|
||||
<mat-label>Effective To</mat-label>
|
||||
<input matInput [matDatepicker]="effectiveToPicker" formControlName="effectiveTo" />
|
||||
<mat-datepicker-toggle matSuffix [for]="effectiveToPicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #effectiveToPicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpd-field" (click)="endDatePicker.open()">
|
||||
<mat-label>End Date</mat-label>
|
||||
<input matInput [matDatepicker]="endDatePicker" formControlName="endDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #endDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpd-field">
|
||||
<mat-label>Introducer Required</mat-label>
|
||||
<mat-select formControlName="introducerRequired">
|
||||
@for (option of yesNoOptions; track option.value) {
|
||||
<mat-option [value]="option.value">{{ option.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="fdpd-field fdpd-field--full fdpd-checkbox-row">
|
||||
<mat-checkbox formControlName="shareLinkingEnabled">Deposit Account Link</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+29
-5
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.fdpd-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -21,19 +27,19 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +55,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fdpd-grid--classification {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpd-field {
|
||||
width: 100%;
|
||||
|
||||
@@ -56,3 +74,9 @@
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpd-checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
+84
-14
@@ -6,8 +6,12 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject, ChangeDetectorRef } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
@@ -15,27 +19,41 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
templateUrl: './fixed-deposit-product-details-step.component.html',
|
||||
styleUrls: ['./fixed-deposit-product-details-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
CdkTextareaAutosize,
|
||||
MatTooltip
|
||||
]
|
||||
})
|
||||
export class FixedDepositProductDetailsStepComponent implements OnInit {
|
||||
export class FixedDepositProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
private cdr = inject(ChangeDetectorRef);
|
||||
|
||||
@Input() fixedDepositProductsTemplate: any;
|
||||
|
||||
fixedDepositProductDetailsForm: UntypedFormGroup;
|
||||
accountTypeData: string[] = [];
|
||||
customerSegmentData: string[] = [];
|
||||
liabilityAccountOptions: any[] = [];
|
||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
||||
{ value: true, label: 'Yes' },
|
||||
{ value: false, label: 'No' }
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createFixedDepositProductDetailsForm();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!(this.fixedDepositProductsTemplate === undefined) && this.fixedDepositProductsTemplate.id) {
|
||||
this.fixedDepositProductDetailsForm.patchValue({
|
||||
name: this.fixedDepositProductsTemplate.name,
|
||||
shortName: this.fixedDepositProductsTemplate.shortName,
|
||||
description: this.fixedDepositProductsTemplate.description
|
||||
});
|
||||
if (this.fixedDepositProductsTemplate) {
|
||||
this.applyTemplate(this.fixedDepositProductsTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.fixedDepositProductsTemplate?.currentValue) {
|
||||
this.applyTemplate(changes.fixedDepositProductsTemplate.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,14 +67,66 @@ export class FixedDepositProductDetailsStepComponent implements OnInit {
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
description: [
|
||||
'',
|
||||
description: [''],
|
||||
accountType: [
|
||||
null,
|
||||
Validators.required
|
||||
]
|
||||
],
|
||||
customerSegment: [null],
|
||||
glAccountId: [null],
|
||||
effectiveTo: [''],
|
||||
endDate: [''],
|
||||
introducerRequired: [false],
|
||||
shareLinkingEnabled: [false]
|
||||
});
|
||||
}
|
||||
|
||||
private getOptionNames(options: any[]): string[] {
|
||||
if (!Array.isArray(options)) return [];
|
||||
return options.map((o) => o?.name ?? o?.value ?? o).filter((v) => typeof v === 'string' && v.trim().length > 0);
|
||||
}
|
||||
|
||||
private parseDate(dateValue: any): Date | null {
|
||||
if (!dateValue) return null;
|
||||
if (Array.isArray(dateValue)) return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]);
|
||||
if (dateValue instanceof Date) return dateValue;
|
||||
return new Date(dateValue);
|
||||
}
|
||||
|
||||
compareById(o1: any, o2: any): boolean {
|
||||
return o1 != null && o2 != null && Number(o1) === Number(o2);
|
||||
}
|
||||
|
||||
private applyTemplate(template: any) {
|
||||
this.accountTypeData = this.getOptionNames(template.accountTypeOptions);
|
||||
this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions);
|
||||
this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
this.cdr.detectChanges();
|
||||
|
||||
const isEditing = !!template.id;
|
||||
this.fixedDepositProductDetailsForm.patchValue({
|
||||
name: isEditing ? template.name : template.name || '',
|
||||
shortName: isEditing ? template.shortName : template.shortName || '',
|
||||
description: template.description || '',
|
||||
accountType: template.accountType ?? this.accountTypeData[0] ?? null,
|
||||
customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null,
|
||||
glAccountId: template.glAccountId ?? null,
|
||||
effectiveTo: this.parseDate(template.effectiveTo),
|
||||
endDate: this.parseDate(template.endDate),
|
||||
introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes',
|
||||
shareLinkingEnabled: !!template.shareLinkingEnabled
|
||||
});
|
||||
}
|
||||
|
||||
get fixedDepositProductDetails() {
|
||||
return this.fixedDepositProductDetailsForm.value;
|
||||
const formValue = { ...this.fixedDepositProductDetailsForm.value };
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
if (formValue.effectiveTo instanceof Date) {
|
||||
formValue.effectiveTo = this.dateUtils.formatDate(formValue.effectiveTo, dateFormat) || '';
|
||||
}
|
||||
if (formValue.endDate instanceof Date) {
|
||||
formValue.endDate = this.dateUtils.formatDate(formValue.endDate, dateFormat) || '';
|
||||
}
|
||||
return formValue;
|
||||
}
|
||||
}
|
||||
|
||||
+105
-185
@@ -7,204 +7,124 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="fixedDepositProductSettingsForm">
|
||||
<!-- Premature option -->
|
||||
<div class="fdps-section">
|
||||
<div class="fdps-section__header">
|
||||
<i class="fas fa-lock"></i>
|
||||
<h3>{{ 'labels.inputs.Lock-in Period' | translate }}</h3>
|
||||
<span class="fdps-section__badge"><i class="fas fa-balance-scale"></i></span>
|
||||
<span>Premature option</span>
|
||||
</div>
|
||||
<div class="fdps-grid">
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
formControlName="lockinPeriodFrequency"
|
||||
matTooltip="{{ 'tooltips.A period of time during which a fixed deposit account' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="lockinPeriodFrequencyType">
|
||||
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
|
||||
<mat-option [value]="lockinPeriodFrequencyType.id">
|
||||
{{ lockinPeriodFrequencyType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div class="fdps-body">
|
||||
<div class="fdps-premature-row">
|
||||
<mat-checkbox formControlName="loanFacility">Loan Facility</mat-checkbox>
|
||||
<mat-checkbox formControlName="prematureWithdrawalOption">Premature Withdrawal option</mat-checkbox>
|
||||
<mat-checkbox formControlName="tenureFlexibility">Tenure Flexibility</mat-checkbox>
|
||||
<mat-checkbox formControlName="lienMarked">Lien Marked</mat-checkbox>
|
||||
<mat-checkbox formControlName="withdrawalFeeForTransfers">Apply Withdrawal Fee for Transfers</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lock-in Period -->
|
||||
<div class="fdps-section">
|
||||
<div class="fdps-section__header">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
<h3>{{ 'labels.inputs.Minimum Deposit Term' | translate }}</h3>
|
||||
<span class="fdps-section__badge"><i class="fas fa-lock"></i></span>
|
||||
<span>{{ 'labels.inputs.Lock-in Period' | translate }}</span>
|
||||
</div>
|
||||
<div class="fdps-grid">
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
formControlName="minDepositTerm"
|
||||
required
|
||||
matTooltip="{{ 'tooltips.The minimum length of time' | translate }}"
|
||||
/>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Minimum Deposit Term Frequency' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="minDepositTermTypeId" required>
|
||||
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
|
||||
<mat-option [value]="periodFrequencyType.id">
|
||||
{{ periodFrequencyType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Minimum Deposit Term Frequency Type' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdps-section">
|
||||
<div class="fdps-section__header">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
<h3>{{ 'labels.heading.And thereafter, in Multiples of' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdps-grid">
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
formControlName="inMultiplesOfDepositTerm"
|
||||
matTooltip="{{ 'tooltips.After the minimum deposit term has passed' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="inMultiplesOfDepositTermTypeId">
|
||||
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
|
||||
<mat-option [value]="periodFrequencyType.id">
|
||||
{{ periodFrequencyType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdps-section">
|
||||
<div class="fdps-section__header">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
<h3>{{ 'labels.inputs.Maximum Deposit Term' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdps-grid">
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
formControlName="maxDepositTerm"
|
||||
matTooltip="{{ 'tooltips.The maximum length of time funds' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="maxDepositTermTypeId">
|
||||
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
|
||||
<mat-option [value]="periodFrequencyType.id">
|
||||
{{ periodFrequencyType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdps-section">
|
||||
<div class="fdps-section__header">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<h3>{{ 'labels.heading.For Pre-mature closure' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdps-grid">
|
||||
<div class="fdps-field fdps-field--full fdps-checkbox-row">
|
||||
<mat-checkbox
|
||||
labelPosition="before"
|
||||
formControlName="preClosurePenalApplicable"
|
||||
matTooltip="{{ 'tooltips.This sections defines an interest penalty' | translate }}"
|
||||
>
|
||||
{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}
|
||||
</mat-checkbox>
|
||||
<div class="fdps-body">
|
||||
<div class="fdps-lockin-row">
|
||||
<span>Enable Lock-in Period</span>
|
||||
<mat-checkbox formControlName="enableLockinPeriod"></mat-checkbox>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Penal Interest' | translate }} (%)</mat-label>
|
||||
<input type="number" min="0" matInput formControlName="preClosurePenalInterest" />
|
||||
</mat-form-field>
|
||||
@if (fixedDepositProductSettingsForm.value.enableLockinPeriod) {
|
||||
<div class="fdps-lockin-fields">
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="lockinPeriodFrequency"
|
||||
min="1"
|
||||
step="1"
|
||||
matTooltip="{{ 'tooltips.A period of time during which a fixed deposit account' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<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 }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdps-section">
|
||||
<div class="fdps-section__header">
|
||||
<i class="fas fa-file-invoice-dollar"></i>
|
||||
<h3>{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdps-grid">
|
||||
<div class="fdps-field fdps-field--full fdps-checkbox-row">
|
||||
<mat-checkbox
|
||||
labelPosition="before"
|
||||
formControlName="withHoldTax"
|
||||
matTooltip="{{ 'tooltips.An boolean flag to attach' | translate }}"
|
||||
>
|
||||
{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductSettingsForm.value.withHoldTax) {
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
|
||||
<mat-select formControlName="taxGroupId" required>
|
||||
@for (taxGroup of taxGroupData; track taxGroup) {
|
||||
<mat-option [value]="taxGroup.id">
|
||||
{{ taxGroup.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Tax Group' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="lockinPeriodFrequencyType">
|
||||
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
|
||||
<mat-option [value]="lockinPeriodFrequencyType.id">
|
||||
{{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Maturity Instructions -->
|
||||
<div class="fdps-section">
|
||||
<div class="fdps-section__header">
|
||||
<span class="fdps-section__badge"><i class="fas fa-lock"></i></span>
|
||||
<span>Maturity Instructions</span>
|
||||
</div>
|
||||
<div class="fdps-body fdps-body--list">
|
||||
<div class="fdps-list-row">
|
||||
<span>Maturity Instructions</span>
|
||||
<mat-checkbox formControlName="maturityInstructions"></mat-checkbox>
|
||||
</div>
|
||||
<div class="fdps-list-row">
|
||||
<span
|
||||
>Option to credit maturity proceeds directly to the linked account or reinvest in another product (like
|
||||
FD).</span
|
||||
>
|
||||
<mat-checkbox formControlName="creditMaturityProceeds"></mat-checkbox>
|
||||
</div>
|
||||
<div class="fdps-list-row">
|
||||
<span>Penalty / Grace Period for Missed Installments</span>
|
||||
<mat-checkbox formControlName="penaltyGracePeriodMissedInstallments"></mat-checkbox>
|
||||
</div>
|
||||
<div class="fdps-list-row">
|
||||
<span>Panel Request Applicable</span>
|
||||
<mat-checkbox formControlName="panelRequestApplicable"></mat-checkbox>
|
||||
</div>
|
||||
<div class="fdps-list-row">
|
||||
<span>No penalty for nominee / legal heir closure</span>
|
||||
<mat-checkbox formControlName="noPenaltyNomineeHeirClosure"></mat-checkbox>
|
||||
</div>
|
||||
<div class="fdps-list-row">
|
||||
<span>Deceased Settlement</span>
|
||||
<mat-checkbox formControlName="deceasedSettlement"></mat-checkbox>
|
||||
</div>
|
||||
<div class="fdps-list-row">
|
||||
<span>Allow Debit Txn after due date</span>
|
||||
<mat-checkbox formControlName="allowDebitTxnAfterDueDate"></mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Rules -->
|
||||
<div class="fdps-section">
|
||||
<div class="fdps-section__header">
|
||||
<span class="fdps-section__badge"><i class="fas fa-cog"></i></span>
|
||||
<span>Additional Rules</span>
|
||||
</div>
|
||||
<div class="fdps-body">
|
||||
<div class="fdps-layout fdps-layout--additional">
|
||||
<div class="fdps-toggle fdps-toggle--inline">
|
||||
<mat-checkbox formControlName="accountClosedZeroBalanceInDayClosing">
|
||||
<span>Account Closed with Zero balance in Day Closing</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdps-field">
|
||||
<mat-label>Balance Required for Interest Calculation</mat-label>
|
||||
<input type="number" matInput formControlName="minBalanceForInterestCalculation" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+112
-16
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.fdps-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -21,32 +27,41 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
span:not(.fdps-section__badge) {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fdps-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
.fdps-section__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.fdps-body {
|
||||
background: #fff;
|
||||
padding: 18px 24px 24px;
|
||||
}
|
||||
|
||||
.fdps-body--list {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.fdps-field {
|
||||
@@ -55,10 +70,91 @@
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
&--compact {
|
||||
width: 200px;
|
||||
min-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdps-checkbox-row {
|
||||
.fdps-premature-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
mat-checkbox {
|
||||
font-size: 14px;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.fdps-list-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
font-size: 14px;
|
||||
color: $gray-900;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
span {
|
||||
max-width: 85%;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.fdps-lockin-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
font-size: 14px;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
.fdps-lockin-fields {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
@media (width <= 640px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.fdps-layout {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.fdps-layout--additional {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
@media (width <= 1100px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.fdps-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
|
||||
mat-checkbox {
|
||||
font-size: 14px;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&--inline {
|
||||
min-height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
+41
-89
@@ -7,13 +7,7 @@
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||
import {
|
||||
UntypedFormGroup,
|
||||
UntypedFormBuilder,
|
||||
UntypedFormControl,
|
||||
Validators,
|
||||
ReactiveFormsModule
|
||||
} from '@angular/forms';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
@@ -36,113 +30,71 @@ export class FixedDepositProductSettingsStepComponent implements OnInit {
|
||||
fixedDepositProductSettingsForm: UntypedFormGroup;
|
||||
|
||||
lockinPeriodFrequencyTypeData: any;
|
||||
periodFrequencyTypeData: any;
|
||||
preClosurePenalInterestOnTypeData: any;
|
||||
taxGroupData: any;
|
||||
|
||||
constructor() {
|
||||
this.createFixedDepositProductSettingsForm();
|
||||
this.setConditionalControls();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.lockinPeriodFrequencyTypeData = this.fixedDepositProductsTemplate.lockinPeriodFrequencyTypeOptions;
|
||||
this.periodFrequencyTypeData = this.fixedDepositProductsTemplate.periodFrequencyTypeOptions.slice(0, -1);
|
||||
this.preClosurePenalInterestOnTypeData = this.fixedDepositProductsTemplate.preClosurePenalInterestOnTypeOptions;
|
||||
this.taxGroupData = this.fixedDepositProductsTemplate.taxGroupOptions;
|
||||
|
||||
if (!(this.fixedDepositProductsTemplate === undefined) && this.fixedDepositProductsTemplate.id) {
|
||||
if (this.fixedDepositProductsTemplate?.id) {
|
||||
this.fixedDepositProductSettingsForm.patchValue({
|
||||
isMandatoryDeposit: this.fixedDepositProductsTemplate.isMandatoryDeposit,
|
||||
adjustAdvanceTowardsFuturePayments: this.fixedDepositProductsTemplate.adjustAdvanceTowardsFuturePayments,
|
||||
allowWithdrawal: this.fixedDepositProductsTemplate.allowWithdrawal,
|
||||
lockinPeriodFrequency: this.fixedDepositProductsTemplate.lockinPeriodFrequency,
|
||||
lockinPeriodFrequencyType: this.fixedDepositProductsTemplate.lockinPeriodFrequencyType
|
||||
? this.fixedDepositProductsTemplate.lockinPeriodFrequencyType.id
|
||||
: '',
|
||||
minDepositTerm: this.fixedDepositProductsTemplate.minDepositTerm,
|
||||
minDepositTermTypeId: this.fixedDepositProductsTemplate.minDepositTermType
|
||||
? this.fixedDepositProductsTemplate.minDepositTermType.id
|
||||
: '',
|
||||
inMultiplesOfDepositTerm: this.fixedDepositProductsTemplate.inMultiplesOfDepositTerm,
|
||||
inMultiplesOfDepositTermTypeId: this.fixedDepositProductsTemplate.inMultiplesOfDepositTermType
|
||||
? this.fixedDepositProductsTemplate.inMultiplesOfDepositTerm.id
|
||||
: '',
|
||||
maxDepositTerm: this.fixedDepositProductsTemplate.maxDepositTerm,
|
||||
maxDepositTermTypeId: this.fixedDepositProductsTemplate.maxDepositTermType
|
||||
? this.fixedDepositProductsTemplate.minDepositTermType.id
|
||||
: '',
|
||||
preClosurePenalApplicable: this.fixedDepositProductsTemplate.preClosurePenalApplicable,
|
||||
preClosurePenalInterest: this.fixedDepositProductsTemplate.preClosurePenalInterest,
|
||||
preClosurePenalInterestOnTypeId: this.fixedDepositProductsTemplate.preClosurePenalInterestOnType
|
||||
? this.fixedDepositProductsTemplate.preClosurePenalInterestOnType.id
|
||||
: '',
|
||||
withHoldTax: this.fixedDepositProductsTemplate.withHoldTax
|
||||
});
|
||||
}
|
||||
|
||||
if (this.fixedDepositProductsTemplate.withHoldTax) {
|
||||
this.fixedDepositProductSettingsForm.patchValue({
|
||||
taxGroupId: this.fixedDepositProductsTemplate.taxGroup ? this.fixedDepositProductsTemplate.taxGroup.id : ''
|
||||
loanFacility: this.fixedDepositProductsTemplate.loanFacility ?? false,
|
||||
prematureWithdrawalOption: this.fixedDepositProductsTemplate.prematureWithdrawalOption ?? false,
|
||||
tenureFlexibility: this.fixedDepositProductsTemplate.tenureFlexibility ?? false,
|
||||
lienMarked: this.fixedDepositProductsTemplate.lienMarked ?? false,
|
||||
withdrawalFeeForTransfers: this.fixedDepositProductsTemplate.withdrawalFeeForTransfers ?? false,
|
||||
enableLockinPeriod: this.fixedDepositProductsTemplate.enableLockinPeriod ?? false,
|
||||
lockinPeriodFrequency: this.fixedDepositProductsTemplate.lockinPeriodFrequency ?? '',
|
||||
lockinPeriodFrequencyType: this.fixedDepositProductsTemplate.lockinPeriodFrequencyType?.id ?? '',
|
||||
maturityInstructions: this.fixedDepositProductsTemplate.maturityInstructions ?? false,
|
||||
creditMaturityProceeds: this.fixedDepositProductsTemplate.creditMaturityProceeds ?? false,
|
||||
penaltyGracePeriodMissedInstallments:
|
||||
this.fixedDepositProductsTemplate.penaltyGracePeriodMissedInstallments ?? false,
|
||||
panelRequestApplicable: this.fixedDepositProductsTemplate.panelRequestApplicable ?? false,
|
||||
noPenaltyNomineeHeirClosure: this.fixedDepositProductsTemplate.noPenaltyNomineeHeirClosure ?? false,
|
||||
deceasedSettlement: this.fixedDepositProductsTemplate.deceasedSettlement ?? false,
|
||||
allowDebitTxnAfterDueDate: this.fixedDepositProductsTemplate.allowDebitTxnAfterDueDate ?? false,
|
||||
accountClosedZeroBalanceInDayClosing:
|
||||
this.fixedDepositProductsTemplate.accountClosedZeroBalanceInDayClosing ?? false,
|
||||
minBalanceForInterestCalculation: this.fixedDepositProductsTemplate.minBalanceForInterestCalculation ?? ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
createFixedDepositProductSettingsForm() {
|
||||
this.fixedDepositProductSettingsForm = this.formBuilder.group({
|
||||
loanFacility: [false],
|
||||
prematureWithdrawalOption: [false],
|
||||
tenureFlexibility: [false],
|
||||
lienMarked: [false],
|
||||
withdrawalFeeForTransfers: [false],
|
||||
enableLockinPeriod: [false],
|
||||
lockinPeriodFrequency: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
lockinPeriodFrequencyType: [''],
|
||||
minDepositTerm: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
minDepositTermTypeId: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
inMultiplesOfDepositTerm: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
inMultiplesOfDepositTermTypeId: [''],
|
||||
maxDepositTerm: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
maxDepositTermTypeId: [''],
|
||||
preClosurePenalApplicable: [false],
|
||||
preClosurePenalInterest: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
preClosurePenalInterestOnTypeId: [''],
|
||||
withHoldTax: [false]
|
||||
});
|
||||
}
|
||||
|
||||
setConditionalControls() {
|
||||
this.fixedDepositProductSettingsForm.get('withHoldTax').valueChanges.subscribe((withHoldTax: any) => {
|
||||
if (withHoldTax) {
|
||||
this.fixedDepositProductSettingsForm.addControl('taxGroupId', new UntypedFormControl('', Validators.required));
|
||||
} else {
|
||||
this.fixedDepositProductSettingsForm.removeControl('taxGroupId');
|
||||
}
|
||||
maturityInstructions: [false],
|
||||
creditMaturityProceeds: [false],
|
||||
penaltyGracePeriodMissedInstallments: [false],
|
||||
panelRequestApplicable: [false],
|
||||
noPenaltyNomineeHeirClosure: [false],
|
||||
deceasedSettlement: [false],
|
||||
allowDebitTxnAfterDueDate: [false],
|
||||
accountClosedZeroBalanceInDayClosing: [false],
|
||||
minBalanceForInterestCalculation: ['']
|
||||
});
|
||||
}
|
||||
|
||||
get fixedDepositProductSettings() {
|
||||
const fixedDepositProductSettings = this.fixedDepositProductSettingsForm.value;
|
||||
for (const key in fixedDepositProductSettings) {
|
||||
if (fixedDepositProductSettings[key] === '') {
|
||||
delete fixedDepositProductSettings[key];
|
||||
const settings = { ...this.fixedDepositProductSettingsForm.value };
|
||||
for (const key in settings) {
|
||||
if (settings[key] === '') {
|
||||
delete settings[key];
|
||||
}
|
||||
}
|
||||
return fixedDepositProductSettings;
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
+134
-125
@@ -7,139 +7,148 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="fixedDepositProductTermsForm">
|
||||
<!-- Interest Rate & Posting Terms -->
|
||||
<div class="fdpt-section">
|
||||
<div class="fdpt-section__header">
|
||||
<i class="fas fa-wallet"></i>
|
||||
<h3>{{ 'labels.inputs.Deposit Amount' | translate }}</h3>
|
||||
<span class="fdpt-section__badge"><i class="fas fa-chart-line"></i></span>
|
||||
<span>Interest Rate & Posting Terms</span>
|
||||
</div>
|
||||
<div class="fdpt-grid">
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Minimum' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
formControlName="minDepositAmount"
|
||||
matTooltip="{{ 'tooltips.The minimum deposit amount required to open a fixed deposit' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
<div class="fdpt-body">
|
||||
<div class="fdpt-banner">
|
||||
<span class="fdpt-banner__icon">ℹ️</span>
|
||||
<span>
|
||||
Fixed Deposit base rate:
|
||||
<b>{{ fixedDepositProductTermsForm.get('nominalAnnualInterestRate').value }}%</b>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Default' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
formControlName="depositAmount"
|
||||
matTooltip="{{ 'tooltips.The default deposit amount expected' | translate }}"
|
||||
required
|
||||
/>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Default' | translate }} {{ 'labels.inputs.Deposit Amount' | translate }}
|
||||
{{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Maximum' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
formControlName="maxDepositAmount"
|
||||
matTooltip="{{ 'tooltips.The maximum deposit amount allowed when a fixed deposit' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdpt-section">
|
||||
<div class="fdpt-section__header">
|
||||
<i class="fas fa-percentage"></i>
|
||||
<h3>{{ 'labels.inputs.Interest Compounding Period' | translate }}</h3>
|
||||
</div>
|
||||
<div class="fdpt-grid">
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Interest Compounding Period' | translate }}</mat-label>
|
||||
<mat-select
|
||||
formControlName="interestCompoundingPeriodType"
|
||||
matTooltip="{{ 'tooltips.The period at which interest rate' | translate }}"
|
||||
required
|
||||
>
|
||||
@for (
|
||||
interestCompoundingPeriodType of interestCompoundingPeriodTypeData;
|
||||
track interestCompoundingPeriodType
|
||||
) {
|
||||
<mat-option [value]="interestCompoundingPeriodType.id">
|
||||
{{ interestCompoundingPeriodType.value }}
|
||||
</mat-option>
|
||||
<div class="fdpt-grid fdpt-grid--three">
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Nominal Annual Interest' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
matTooltip="{{ 'tooltips.The default interest rate set' | translate }}"
|
||||
formControlName="nominalAnnualInterestRate"
|
||||
required
|
||||
min="0"
|
||||
step="0.01"
|
||||
/>
|
||||
@if (fixedDepositProductTermsForm.get('nominalAnnualInterestRate').hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Nominal Annual Interest' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
|
||||
<mat-select
|
||||
formControlName="interestPostingPeriodType"
|
||||
matTooltip="{{ 'tooltips.The period at which interest rate is posted or credited' | translate }}"
|
||||
required
|
||||
>
|
||||
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
|
||||
<mat-option [value]="interestPostingPeriodType.id">
|
||||
{{ interestPostingPeriodType.value }}
|
||||
</mat-option>
|
||||
@if (fixedDepositProductTermsForm.get('nominalAnnualInterestRate').hasError('min')) {
|
||||
<mat-error>{{ 'labels.inputs.Nominal Annual Interest' | translate }} must be zero or greater</mat-error>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Interest Calculated using' | translate }}</mat-label>
|
||||
<mat-select
|
||||
formControlName="interestCalculationType"
|
||||
matTooltip="{{ 'tooltips.The method used to calculate interest' | translate }}"
|
||||
required
|
||||
>
|
||||
@for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) {
|
||||
<mat-option [value]="interestCalculationType.id">
|
||||
{{ interestCalculationType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Effective Date</mat-label>
|
||||
<input matInput [matDatepicker]="effectiveDatePicker" formControlName="effectiveDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="effectiveDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #effectiveDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Days in Year' | translate }}</mat-label>
|
||||
<mat-select
|
||||
formControlName="interestCalculationDaysInYearType"
|
||||
matTooltip="{{ 'tooltips.The setting for number of days' | translate }}"
|
||||
required
|
||||
>
|
||||
@for (
|
||||
interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData;
|
||||
track interestCalculationDaysInYearType
|
||||
) {
|
||||
<mat-option [value]="interestCalculationDaysInYearType.id">
|
||||
{{ interestCalculationDaysInYearType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Interest Calculation Type' | translate }}</mat-label>
|
||||
<mat-select
|
||||
formControlName="interestCalculateSelector"
|
||||
matTooltip="{{ 'tooltips.The method used to calculate interest' | translate }}"
|
||||
>
|
||||
@for (opt of interestCalculationTypeOptionData; track opt.id) {
|
||||
<mat-option [value]="opt.id">{{ opt.value }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="fdpt-grid fdpt-grid--three">
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Interest Calculate</mat-label>
|
||||
<mat-select formControlName="interestCalculationType" required>
|
||||
@for (mode of interestCalculationTypeData; track mode.id) {
|
||||
<mat-option [value]="mode.id">{{ mode.value | translateKey: 'catalogs' }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Interest Calculation Type' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@if (fixedDepositProductTermsForm.value.interestCalculationType === 2) {
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Average Period</mat-label>
|
||||
<mat-select formControlName="averagePeriod">
|
||||
@for (period of periodFrequencyData; track period.id) {
|
||||
<mat-option [value]="period.value">{{ period.value }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
|
||||
<mat-select
|
||||
formControlName="interestPostingPeriodType"
|
||||
matTooltip="{{ 'tooltips.Interest rate is posted or credited to a saving account' | translate }}"
|
||||
required
|
||||
>
|
||||
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
|
||||
<mat-option [value]="interestPostingPeriodType.id">
|
||||
{{ interestPostingPeriodType.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="fdpt-footer">
|
||||
<div class="fdpt-footer__grid">
|
||||
<div class="fdpt-toggle-card">
|
||||
<div>
|
||||
<h4>Auto Interest Post</h4>
|
||||
</div>
|
||||
<div class="fdpt-toggle-card__choices">
|
||||
@for (option of yesNoOptions; track option.value) {
|
||||
<label class="inline-flex items-center cursor-pointer">
|
||||
<input type="radio" class="w-4 h-4" formControlName="autoPost" [value]="option.value" />
|
||||
<span class="ml-2 text-sm">{{ option.label }}</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Frequency (Fix Day)</mat-label>
|
||||
<input type="number" matInput formControlName="frequencyFixDay" />
|
||||
<mat-hint>Days</mat-hint>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Last Interest Date</mat-label>
|
||||
<input matInput [matDatepicker]="lastInterestDatePicker" formControlName="lastInterestDate" />
|
||||
<mat-datepicker-toggle matSuffix [for]="lastInterestDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #lastInterestDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductTermsForm.value.interestCalculationType === 1) {
|
||||
<div class="fdpt-note">
|
||||
<span>⚠️</span>
|
||||
<span
|
||||
>Daily product-based interest accrual enabled - interest calculated EOD on daily closing balance.</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+99
-6
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.fdpt-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -21,31 +27,54 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
span:not(.fdpt-section__badge) {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fdpt-section__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fdpt-body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.fdpt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
&--three {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
&--three {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,3 +85,67 @@
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpt-footer {
|
||||
padding: 0 24px 24px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.fdpt-footer__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
padding-top: 18px;
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpt-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 16px 24px 0;
|
||||
padding: 12px 14px;
|
||||
background: #eff6ff;
|
||||
border: 1px solid #dbeafe;
|
||||
border-radius: 8px;
|
||||
color: #1d4ed8;
|
||||
font-size: 13px;
|
||||
|
||||
&__icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpt-toggle-card {
|
||||
padding: 12px 0;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&__choices {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpt-note {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid #fde68a;
|
||||
background: #fffbeb;
|
||||
color: #b45309;
|
||||
border-radius: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
+76
-12
@@ -7,8 +7,10 @@
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
@@ -22,6 +24,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
})
|
||||
export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
@Input() fixedDepositProductsTemplate: any;
|
||||
|
||||
@@ -30,7 +34,13 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
interestCompoundingPeriodTypeData: any;
|
||||
interestPostingPeriodTypeData: any;
|
||||
interestCalculationTypeData: any;
|
||||
interestCalculationTypeOptionData: any;
|
||||
interestCalculationDaysInYearTypeData: any;
|
||||
periodFrequencyData: any[] = [];
|
||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
||||
{ value: true, label: 'Yes' },
|
||||
{ value: false, label: 'No' }
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createFixedDepositProductTermsForm();
|
||||
@@ -40,10 +50,22 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
this.interestCompoundingPeriodTypeData = this.fixedDepositProductsTemplate.interestCompoundingPeriodTypeOptions;
|
||||
this.interestPostingPeriodTypeData = this.fixedDepositProductsTemplate.interestPostingPeriodTypeOptions;
|
||||
this.interestCalculationTypeData = this.fixedDepositProductsTemplate.interestCalculationTypeOptions;
|
||||
this.interestCalculationTypeOptionData = this.fixedDepositProductsTemplate.interestCalculationTypeOption || [];
|
||||
this.interestCalculationDaysInYearTypeData =
|
||||
this.fixedDepositProductsTemplate.interestCalculationDaysInYearTypeOptions;
|
||||
this.periodFrequencyData = this.fixedDepositProductsTemplate.periodFrequencyOptions || [];
|
||||
|
||||
if (!(this.fixedDepositProductsTemplate === undefined) && this.fixedDepositProductsTemplate.id) {
|
||||
const isEditing = !!this.fixedDepositProductsTemplate.id;
|
||||
const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly';
|
||||
const savedEnumTypeId =
|
||||
this.fixedDepositProductsTemplate.interestCalculationType?.id || this.interestCalculationTypeData?.[0]?.id || 1;
|
||||
const savedSelectorId = isEditing
|
||||
? (this.fixedDepositProductsTemplate.interestCalculateSelector ??
|
||||
this.interestCalculationTypeOptionData?.[0]?.id ??
|
||||
null)
|
||||
: (this.interestCalculationTypeOptionData?.[0]?.id ?? null);
|
||||
|
||||
if (isEditing) {
|
||||
this.fixedDepositProductTermsForm.patchValue({
|
||||
minDepositAmount: this.fixedDepositProductsTemplate.minDepositAmount,
|
||||
depositAmount: this.fixedDepositProductsTemplate.depositAmount,
|
||||
@@ -52,13 +74,31 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
}
|
||||
|
||||
this.fixedDepositProductTermsForm.patchValue({
|
||||
interestCompoundingPeriodType: this.fixedDepositProductsTemplate.interestCompoundingPeriodType.id,
|
||||
interestPostingPeriodType: this.fixedDepositProductsTemplate.interestPostingPeriodType.id,
|
||||
interestCalculationType: this.fixedDepositProductsTemplate.interestCalculationType.id,
|
||||
interestCalculationDaysInYearType: this.fixedDepositProductsTemplate.interestCalculationDaysInYearType.id
|
||||
nominalAnnualInterestRate: this.fixedDepositProductsTemplate.nominalAnnualInterestRate ?? 0,
|
||||
interestCompoundingPeriodType: this.fixedDepositProductsTemplate.interestCompoundingPeriodType?.id,
|
||||
interestPostingPeriodType: this.fixedDepositProductsTemplate.interestPostingPeriodType?.id,
|
||||
interestCalculationType: savedEnumTypeId,
|
||||
interestCalculationDaysInYearType: this.fixedDepositProductsTemplate.interestCalculationDaysInYearType?.id,
|
||||
effectiveDate: this.fixedDepositProductsTemplate.effectiveDate
|
||||
? this.parseDate(this.fixedDepositProductsTemplate.effectiveDate)
|
||||
: '',
|
||||
interestCalculateSelector: savedSelectorId,
|
||||
averagePeriod: isEditing ? this.fixedDepositProductsTemplate.averagePeriod || defaultPeriod : defaultPeriod,
|
||||
autoPost: isEditing ? (this.fixedDepositProductsTemplate.autoPost ?? true) : true,
|
||||
frequencyFixDay: isEditing ? (this.fixedDepositProductsTemplate.frequencyFixDay ?? 30) : 30,
|
||||
lastInterestDate: this.fixedDepositProductsTemplate.lastInterestDate
|
||||
? this.parseDate(this.fixedDepositProductsTemplate.lastInterestDate)
|
||||
: ''
|
||||
});
|
||||
}
|
||||
|
||||
private parseDate(dateValue: any): Date | null {
|
||||
if (!dateValue) return null;
|
||||
if (Array.isArray(dateValue)) return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]);
|
||||
if (dateValue instanceof Date) return dateValue;
|
||||
return new Date(dateValue);
|
||||
}
|
||||
|
||||
createFixedDepositProductTermsForm() {
|
||||
this.fixedDepositProductTermsForm = this.formBuilder.group({
|
||||
minDepositAmount: [
|
||||
@@ -76,6 +116,13 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
nominalAnnualInterestRate: [
|
||||
'',
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
interestCompoundingPeriodType: [
|
||||
'',
|
||||
Validators.required
|
||||
@@ -91,17 +138,34 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
interestCalculationDaysInYearType: [
|
||||
'',
|
||||
Validators.required
|
||||
]
|
||||
],
|
||||
effectiveDate: [''],
|
||||
interestCalculateSelector: [1],
|
||||
averagePeriod: ['Monthly'],
|
||||
autoPost: [true],
|
||||
frequencyFixDay: [
|
||||
30,
|
||||
[Validators.min(0)]
|
||||
],
|
||||
lastInterestDate: ['']
|
||||
});
|
||||
}
|
||||
|
||||
get fixedDepositProductTerms() {
|
||||
const fixedDepositProductTerms = this.fixedDepositProductTermsForm.value;
|
||||
for (const key in fixedDepositProductTerms) {
|
||||
if (fixedDepositProductTerms[key] === '') {
|
||||
delete fixedDepositProductTerms[key];
|
||||
const formValue = { ...this.fixedDepositProductTermsForm.value };
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
if (formValue.effectiveDate instanceof Date) {
|
||||
formValue.effectiveDate = this.dateUtils.formatDate(formValue.effectiveDate, dateFormat) || '';
|
||||
}
|
||||
if (formValue.lastInterestDate instanceof Date) {
|
||||
formValue.lastInterestDate = this.dateUtils.formatDate(formValue.lastInterestDate, dateFormat) || '';
|
||||
}
|
||||
for (const key in formValue) {
|
||||
if (formValue[key] === '') {
|
||||
delete formValue[key];
|
||||
}
|
||||
}
|
||||
return fixedDepositProductTerms;
|
||||
return formValue;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user