fd product releated update code
This commit is contained in:
+28
-12
@@ -24,7 +24,9 @@
|
||||
{{ 'labels.inputs.DETAILS' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-details-step></mifosx-fixed-deposit-product-details-step>
|
||||
<mifosx-fixed-deposit-product-details-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
></mifosx-fixed-deposit-product-details-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -48,6 +50,7 @@
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-terms-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[fdType]="fixedDepositProductDetailsStep?.fixedDepositProductDetailsForm?.get('fdType')?.value"
|
||||
></mifosx-fixed-deposit-product-terms-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
@@ -147,27 +150,40 @@
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[fixedDepositProductFormValid]="fixedDepositProductFormValid"
|
||||
[isDormancyTrackingActive]="fixedDepositProductCurrencyForm.get('dormancyAlert')"
|
||||
></mifosx-fixed-deposit-product-accounting-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@if (fixedDepositProductFormValid) {
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-eye fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.PREVIEW' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-eye fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.PREVIEW' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
@if (fixedDepositProductFormValid) {
|
||||
<mifosx-fixed-deposit-product-preview-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[chartSlabsDisplayedColumns]="fdSlabColumns"
|
||||
[chartSlabsDisplayedColumns]="[previewChartSlabColumns]"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[fixedDepositProduct]="fixedDepositProduct"
|
||||
(submitEvent)="submit()"
|
||||
></mifosx-fixed-deposit-product-preview-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
}
|
||||
} @else {
|
||||
<div style="padding: 32px; text-align: center; color: #666">
|
||||
<i
|
||||
class="fas fa-exclamation-triangle"
|
||||
style="font-size: 32px; color: #f44336; margin-bottom: 12px; display: block"
|
||||
></i>
|
||||
<h3 style="margin-bottom: 8px">Please complete all required fields</h3>
|
||||
<p>
|
||||
Look for the <i class="fas fa-exclamation-circle" style="color: #f44336"></i> icon on each tab to find
|
||||
which steps still need attention.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
|
||||
<div class="fdp-action-bar">
|
||||
|
||||
+109
-19
@@ -33,6 +33,7 @@ import {
|
||||
/** Custom Services */
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { FixedDepositProductPreviewStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component';
|
||||
@@ -71,6 +72,7 @@ export class CreateFixedDepositProductComponent {
|
||||
private productsService = inject(ProductsService);
|
||||
private router = inject(Router);
|
||||
private settingsService = inject(SettingsService);
|
||||
private dateUtils = inject(Dates);
|
||||
private accounting = inject(Accounting);
|
||||
|
||||
@ViewChild(FixedDepositProductDetailsStepComponent, { static: true })
|
||||
@@ -98,13 +100,20 @@ export class CreateFixedDepositProductComponent {
|
||||
'periodTypeLabel',
|
||||
'annualInterestRate'
|
||||
];
|
||||
readonly previewChartSlabColumns = [
|
||||
'period',
|
||||
'amountRange',
|
||||
'annualInterestRate',
|
||||
'description',
|
||||
'actions'
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { fixedDepositProductsTemplate: any }) => {
|
||||
this.fixedDepositProductsTemplate = data.fixedDepositProductsTemplate;
|
||||
});
|
||||
this.accountingRuleData = this.accounting.getAccountingRulesForSavings();
|
||||
this.productsService.getRdBaseInterestRates().subscribe({
|
||||
this.productsService.getFdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => {
|
||||
this.fdInterestRates = rates || [];
|
||||
},
|
||||
@@ -115,41 +124,41 @@ export class CreateFixedDepositProductComponent {
|
||||
}
|
||||
|
||||
get fixedDepositProductDetailsForm() {
|
||||
return this.fixedDepositProductDetailsStep.fixedDepositProductDetailsForm;
|
||||
return this.fixedDepositProductDetailsStep?.fixedDepositProductDetailsForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductCurrencyForm() {
|
||||
return this.fixedDepositProductCurrencyStep.fixedDepositProductCurrencyForm;
|
||||
return this.fixedDepositProductCurrencyStep?.fixedDepositProductCurrencyForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductTermsForm() {
|
||||
return this.fixedDepositProductTermsStep.fixedDepositProductTermsForm;
|
||||
return this.fixedDepositProductTermsStep?.fixedDepositProductTermsForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductSettingsForm() {
|
||||
return this.fixedDepositProductSettingsStep.fixedDepositProductSettingsForm;
|
||||
return this.fixedDepositProductSettingsStep?.fixedDepositProductSettingsForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductAccountingForm() {
|
||||
return this.fixedDepositProductAccountingStep.fixedDepositProductAccountingForm;
|
||||
return this.fixedDepositProductAccountingStep?.fixedDepositProductAccountingForm;
|
||||
}
|
||||
|
||||
get totalTabs(): number {
|
||||
return this.fixedDepositProductFormValid ? 8 : 7;
|
||||
return 8;
|
||||
}
|
||||
|
||||
get fixedDepositProductFormValid() {
|
||||
return (
|
||||
this.fixedDepositProductDetailsForm.valid &&
|
||||
this.fixedDepositProductCurrencyForm.valid &&
|
||||
this.fixedDepositProductTermsForm.valid &&
|
||||
this.fixedDepositProductSettingsForm.valid &&
|
||||
this.fixedDepositProductAccountingForm.valid
|
||||
);
|
||||
return !!this.fixedDepositProductDetailsForm?.valid;
|
||||
}
|
||||
|
||||
get selectedFdType(): string | null {
|
||||
return this.fixedDepositProductDetailsStep?.fixedDepositProductDetailsForm?.get('fdType')?.value ?? null;
|
||||
}
|
||||
|
||||
get activeBaseVersion() {
|
||||
return this.fdInterestRates.find((v: any) => v.isActive) ?? this.fdInterestRates[0] ?? null;
|
||||
const fdType = this.selectedFdType;
|
||||
const filtered = fdType ? this.fdInterestRates.filter((v: any) => v.fdType === fdType) : this.fdInterestRates;
|
||||
return filtered.find((v: any) => v.isActive) ?? filtered[0] ?? null;
|
||||
}
|
||||
|
||||
get activeBaseSlabs() {
|
||||
@@ -164,16 +173,97 @@ export class CreateFixedDepositProductComponent {
|
||||
...this.fixedDepositProductSettingsStep.fixedDepositProductSettings,
|
||||
...this.fixedDepositProductChargesStep.fixedDepositProductCharges,
|
||||
...this.fixedDepositProductAccountingStep.fixedDepositProductAccounting,
|
||||
fdSlabs: this.activeBaseSlabs
|
||||
charts: this.fixedDepositProductCharts,
|
||||
fdSlabs: this.activeBaseVersion?.slabs || []
|
||||
};
|
||||
}
|
||||
|
||||
get fixedDepositProductCharts() {
|
||||
const activeBaseVersion = this.activeBaseVersion;
|
||||
if (!activeBaseVersion?.slabs?.length) {
|
||||
return [];
|
||||
}
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
return [
|
||||
{
|
||||
name: activeBaseVersion.versionLabel || 'CBS Base Chart',
|
||||
description: activeBaseVersion.remarks || activeBaseVersion.versionLabel || 'CBS base chart',
|
||||
fromDate:
|
||||
this.dateUtils.formatDate(activeBaseVersion.effectiveFromDate, dateFormat) ||
|
||||
activeBaseVersion.effectiveFromDate,
|
||||
...(activeBaseVersion.effectiveToDate
|
||||
? {
|
||||
endDate:
|
||||
this.dateUtils.formatDate(activeBaseVersion.effectiveToDate, dateFormat) ||
|
||||
activeBaseVersion.effectiveToDate
|
||||
}
|
||||
: {}),
|
||||
isPrimaryGroupingByAmount: false,
|
||||
locale: this.settingsService.language.code,
|
||||
dateFormat,
|
||||
chartSlabs: activeBaseVersion.slabs.map((slab: any) => {
|
||||
const chartSlab: any = {
|
||||
periodType: slab.periodTypeEnum,
|
||||
fromPeriod: slab.fromPeriod,
|
||||
toPeriod: slab.toPeriod,
|
||||
annualInterestRate: slab.annualInterestRate,
|
||||
description: slab.description || '',
|
||||
incentives: [] as any[]
|
||||
};
|
||||
|
||||
if (slab.amountRangeFrom !== null && slab.amountRangeFrom !== undefined && slab.amountRangeFrom !== '') {
|
||||
chartSlab.amountRangeFrom = slab.amountRangeFrom;
|
||||
}
|
||||
|
||||
if (slab.amountRangeTo !== null && slab.amountRangeTo !== undefined && slab.amountRangeTo !== '') {
|
||||
chartSlab.amountRangeTo = slab.amountRangeTo;
|
||||
}
|
||||
|
||||
return chartSlab;
|
||||
})
|
||||
}
|
||||
] as any[];
|
||||
}
|
||||
|
||||
private pruneEmptyValues<T>(value: T): T {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => this.pruneEmptyValues(item)).filter((item) => item !== undefined) as T;
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.entries(value as Record<string, any>).reduce(
|
||||
(acc, [
|
||||
key,
|
||||
currentValue
|
||||
]) => {
|
||||
const cleanedValue = this.pruneEmptyValues(currentValue);
|
||||
if (cleanedValue === undefined) {
|
||||
return acc;
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[key]: cleanedValue
|
||||
};
|
||||
},
|
||||
{} as Record<string, any>
|
||||
) as T;
|
||||
}
|
||||
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
submit() {
|
||||
const fixedDepositProduct = {
|
||||
const fixedDepositProduct: any = this.pruneEmptyValues({
|
||||
...this.fixedDepositProduct,
|
||||
charges: this.fixedDepositProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||
charts: this.fixedDepositProductCharts,
|
||||
charges: (this.fixedDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code
|
||||
};
|
||||
});
|
||||
delete fixedDepositProduct.advancedAccountingRules;
|
||||
this.productsService.createFixedDepositProduct(fixedDepositProduct).subscribe((response: any) => {
|
||||
this.router.navigate(
|
||||
|
||||
+189
-96
@@ -6,104 +6,197 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="container">
|
||||
<mat-stepper
|
||||
orientation="vertical"
|
||||
[linear]="false"
|
||||
class="mat-elevation-z8"
|
||||
labelPosition="bottom"
|
||||
#fixedDepositProductStepper
|
||||
<div class="fdp-container">
|
||||
<div class="fdp-header">
|
||||
<i class="fas fa-piggy-bank fdp-header__icon"></i>
|
||||
<h2 class="fdp-header__title">{{ 'labels.heading.Edit Fixed Deposit Product' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<mat-tab-group
|
||||
class="fdp-tabs"
|
||||
animationDuration="200ms"
|
||||
[preserveContent]="true"
|
||||
[(selectedIndex)]="selectedTabIndex"
|
||||
>
|
||||
<ng-template matStepperIcon="number">
|
||||
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="edit">
|
||||
<fa-icon icon="pencil-alt" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="done">
|
||||
<fa-icon icon="check" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="error">
|
||||
<fa-icon icon="exclamation-triangle" size="lg"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="preview">
|
||||
<fa-icon icon="eye" size="sm"></fa-icon>
|
||||
</ng-template>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositProductDetailsForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-product-details-step [fixedDepositProductsTemplate]="fixedDepositProductsTemplate">
|
||||
</mifosx-fixed-deposit-product-details-step>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositProductCurrencyForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CURRENCY' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-product-currency-step [fixedDepositProductsTemplate]="fixedDepositProductsTemplate">
|
||||
</mifosx-fixed-deposit-product-currency-step>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositProductTermsForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-product-terms-step [fixedDepositProductsTemplate]="fixedDepositProductsTemplate">
|
||||
</mifosx-fixed-deposit-product-terms-step>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositProductSettingsForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-product-settings-step [fixedDepositProductsTemplate]="fixedDepositProductsTemplate">
|
||||
</mifosx-fixed-deposit-product-settings-step>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositProductInterestRateChartForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.INTEREST RATE CHART' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-product-interest-rate-chart-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
>
|
||||
</mifosx-fixed-deposit-product-interest-rate-chart-step>
|
||||
</mat-step>
|
||||
|
||||
<mat-step>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-product-charges-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[currencyCode]="fixedDepositProductCurrencyForm.get('currencyCode')"
|
||||
>
|
||||
</mifosx-fixed-deposit-product-charges-step>
|
||||
</mat-step>
|
||||
|
||||
<mat-step [stepControl]="fixedDepositProductAccountingForm">
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.ACCOUNTING' | translate }}</ng-template>
|
||||
|
||||
<mifosx-fixed-deposit-product-accounting-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[fixedDepositProductFormValid]="fixedDepositProductFormValidAndNotPrinstine"
|
||||
>
|
||||
</mifosx-fixed-deposit-product-accounting-step>
|
||||
</mat-step>
|
||||
|
||||
@if (fixedDepositProductFormValidAndNotPrinstine) {
|
||||
<mat-step state="preview" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||
<mifosx-fixed-deposit-product-preview-step
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-info-circle fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.DETAILS' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-details-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
></mifosx-fixed-deposit-product-details-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-coins fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.CURRENCY' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-currency-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
></mifosx-fixed-deposit-product-currency-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-sliders-h fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.TERMS' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-terms-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[fdType]="fixedDepositProductDetailsStep?.fixedDepositProductDetailsForm?.get('fdType')?.value"
|
||||
></mifosx-fixed-deposit-product-terms-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-cog fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.SETTINGS' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-settings-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
></mifosx-fixed-deposit-product-settings-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-receipt fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.CHARGES' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-charges-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[currencyCode]="fixedDepositProductCurrencyForm?.get('currencyCode')"
|
||||
></mifosx-fixed-deposit-product-charges-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-chart-area fdp-tab-icon"></i>
|
||||
CBS Base Chart
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
@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>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-book fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.ACCOUNTING' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-accounting-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[chartSlabsDisplayedColumns]="fixedDepositProductInterestRateChartStep.chartSlabsDisplayedColumns"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[fixedDepositProduct]="fixedDepositProduct"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
</mifosx-fixed-deposit-product-preview-step>
|
||||
</mat-step>
|
||||
[fixedDepositProductFormValid]="fixedDepositProductFormValid"
|
||||
[isDormancyTrackingActive]="fixedDepositProductCurrencyForm?.get('dormancyAlert')"
|
||||
></mifosx-fixed-deposit-product-accounting-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@if (fixedDepositProductFormValid) {
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-eye fdp-tab-icon"></i>
|
||||
{{ 'labels.inputs.PREVIEW' | translate }}
|
||||
</ng-template>
|
||||
<div class="fdp-tab-content">
|
||||
<mifosx-fixed-deposit-product-preview-step
|
||||
[fixedDepositProductsTemplate]="fixedDepositProductsTemplate"
|
||||
[chartSlabsDisplayedColumns]="[previewChartSlabColumns]"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[fixedDepositProduct]="fixedDepositProduct"
|
||||
(submitEvent)="submit()"
|
||||
></mifosx-fixed-deposit-product-preview-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
}
|
||||
</mat-stepper>
|
||||
</mat-tab-group>
|
||||
|
||||
<div class="fdp-action-bar">
|
||||
<button mat-raised-button [routerLink]="['../../']" class="fdp-action-bar__cancel">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<div class="fdp-action-bar__right">
|
||||
@if (selectedTabIndex > 0) {
|
||||
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
||||
<i class="fas fa-arrow-left" style="margin-right: 8px"></i>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (selectedTabIndex < totalTabs - 1) {
|
||||
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<i class="fas fa-arrow-right" style="margin-left: 8px"></i>
|
||||
</button>
|
||||
}
|
||||
@if (fixedDepositProductFormValid && selectedTabIndex === totalTabs - 1) {
|
||||
<button mat-raised-button color="primary" (click)="submit()">
|
||||
<i class="fas fa-check" style="margin-right: 8px"></i>
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+131
@@ -5,3 +5,134 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$primary: #1353a0;
|
||||
|
||||
.fdp-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.fdp-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__icon {
|
||||
font-size: 20px;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.fdp-tabs {
|
||||
background: var(--md-sys-color-surface, #fff);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||
|
||||
::ng-deep {
|
||||
.mat-mdc-tab-header {
|
||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
.mat-mdc-tab {
|
||||
min-width: 120px;
|
||||
padding: 0 16px;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-wrapper {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fdp-tab-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fdp-tab-content {
|
||||
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;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
|
||||
&__cancel {
|
||||
color: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
+200
-55
@@ -15,15 +15,27 @@ 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';
|
||||
|
||||
/** Custom Services */
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { FixedDepositProductPreviewStepComponent } from '../fixed-deposit-product-stepper/fixed-deposit-product-preview-step/fixed-deposit-product-preview-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
@@ -34,16 +46,22 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
styleUrls: ['./edit-fixed-deposit-product.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
MatTabsModule,
|
||||
FaIconComponent,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
FixedDepositProductDetailsStepComponent,
|
||||
FixedDepositProductCurrencyStepComponent,
|
||||
FixedDepositProductTermsStepComponent,
|
||||
FixedDepositProductSettingsStepComponent,
|
||||
FixedDepositProductInterestRateChartStepComponent,
|
||||
FixedDepositProductChargesStepComponent,
|
||||
FixedDepositProductAccountingStepComponent,
|
||||
FixedDepositProductPreviewStepComponent
|
||||
@@ -55,6 +73,7 @@ export class EditFixedDepositProductComponent {
|
||||
private router = inject(Router);
|
||||
private settingsService = inject(SettingsService);
|
||||
private accounting = inject(Accounting);
|
||||
private dateUtils = inject(Dates);
|
||||
|
||||
@ViewChild(FixedDepositProductDetailsStepComponent, { static: true })
|
||||
fixedDepositProductDetailsStep: FixedDepositProductDetailsStepComponent;
|
||||
@@ -64,8 +83,6 @@ export class EditFixedDepositProductComponent {
|
||||
fixedDepositProductTermsStep: FixedDepositProductTermsStepComponent;
|
||||
@ViewChild(FixedDepositProductSettingsStepComponent, { static: true })
|
||||
fixedDepositProductSettingsStep: FixedDepositProductSettingsStepComponent;
|
||||
@ViewChild(FixedDepositProductInterestRateChartStepComponent, { static: true })
|
||||
fixedDepositProductInterestRateChartStep: FixedDepositProductInterestRateChartStepComponent;
|
||||
@ViewChild(FixedDepositProductChargesStepComponent, { static: true })
|
||||
fixedDepositProductChargesStep: FixedDepositProductChargesStepComponent;
|
||||
@ViewChild(FixedDepositProductAccountingStepComponent, { static: true })
|
||||
@@ -73,6 +90,23 @@ export class EditFixedDepositProductComponent {
|
||||
|
||||
fixedDepositProductsTemplate: any;
|
||||
accountingRuleData: string[] = [];
|
||||
selectedTabIndex = 0;
|
||||
fdInterestRates: any[] = [];
|
||||
readonly fdSlabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
'fromPeriod',
|
||||
'toPeriod',
|
||||
'periodTypeLabel',
|
||||
'annualInterestRate'
|
||||
];
|
||||
readonly previewChartSlabColumns = [
|
||||
'period',
|
||||
'amountRange',
|
||||
'annualInterestRate',
|
||||
'description',
|
||||
'actions'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
@@ -86,47 +120,42 @@ export class EditFixedDepositProductComponent {
|
||||
this.fixedDepositProductsTemplate = data.fixedDepositProductAndTemplate;
|
||||
});
|
||||
this.accountingRuleData = this.accounting.getAccountingRulesForSavings();
|
||||
this.productsService.getFdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => {
|
||||
this.fdInterestRates = rates || [];
|
||||
},
|
||||
error: () => {
|
||||
this.fdInterestRates = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get fixedDepositProductDetailsForm() {
|
||||
return this.fixedDepositProductDetailsStep.fixedDepositProductDetailsForm;
|
||||
return this.fixedDepositProductDetailsStep?.fixedDepositProductDetailsForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductCurrencyForm() {
|
||||
return this.fixedDepositProductCurrencyStep.fixedDepositProductCurrencyForm;
|
||||
return this.fixedDepositProductCurrencyStep?.fixedDepositProductCurrencyForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductTermsForm() {
|
||||
return this.fixedDepositProductTermsStep.fixedDepositProductTermsForm;
|
||||
return this.fixedDepositProductTermsStep?.fixedDepositProductTermsForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductSettingsForm() {
|
||||
return this.fixedDepositProductSettingsStep.fixedDepositProductSettingsForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductInterestRateChartForm() {
|
||||
return this.fixedDepositProductInterestRateChartStep.fixedDepositProductInterestRateChartForm;
|
||||
return this.fixedDepositProductSettingsStep?.fixedDepositProductSettingsForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductAccountingForm() {
|
||||
return this.fixedDepositProductAccountingStep.fixedDepositProductAccountingForm;
|
||||
return this.fixedDepositProductAccountingStep?.fixedDepositProductAccountingForm;
|
||||
}
|
||||
|
||||
get fixedDepositProductFormValidAndNotPrinstine() {
|
||||
return (
|
||||
this.fixedDepositProductDetailsForm.valid &&
|
||||
this.fixedDepositProductCurrencyForm.valid &&
|
||||
this.fixedDepositProductTermsForm.valid &&
|
||||
this.fixedDepositProductSettingsForm.valid &&
|
||||
this.fixedDepositProductInterestRateChartForm.valid &&
|
||||
this.fixedDepositProductAccountingForm.valid &&
|
||||
(this.fixedDepositProductDetailsForm.pristine ||
|
||||
this.fixedDepositProductCurrencyForm.pristine ||
|
||||
this.fixedDepositProductTermsForm.pristine ||
|
||||
this.fixedDepositProductSettingsForm.pristine ||
|
||||
this.fixedDepositProductInterestRateChartForm.pristine ||
|
||||
this.fixedDepositProductAccountingForm.pristine)
|
||||
);
|
||||
get totalTabs(): number {
|
||||
return this.fixedDepositProductFormValid ? 8 : 7;
|
||||
}
|
||||
|
||||
get fixedDepositProductFormValid() {
|
||||
return !!this.fixedDepositProductDetailsForm?.valid;
|
||||
}
|
||||
|
||||
get fixedDepositProduct() {
|
||||
@@ -135,33 +164,118 @@ export class EditFixedDepositProductComponent {
|
||||
...this.fixedDepositProductCurrencyStep.fixedDepositProductCurrency,
|
||||
...this.fixedDepositProductTermsStep.fixedDepositProductTerms,
|
||||
...this.fixedDepositProductSettingsStep.fixedDepositProductSettings,
|
||||
...this.fixedDepositProductInterestRateChartStep.fixedDepositProductInterestRateChart,
|
||||
...this.fixedDepositProductChargesStep.fixedDepositProductCharges,
|
||||
...this.fixedDepositProductAccountingStep.fixedDepositProductAccounting
|
||||
...this.fixedDepositProductAccountingStep.fixedDepositProductAccounting,
|
||||
charts: this.fixedDepositProductCharts,
|
||||
fdSlabs: this.activeBaseVersion?.slabs || []
|
||||
};
|
||||
}
|
||||
|
||||
submit() {
|
||||
const fixedDepositProduct = {
|
||||
...this.fixedDepositProduct,
|
||||
charges: this.fixedDepositProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code // locale required for depositAmount
|
||||
};
|
||||
if (!fixedDepositProduct.description) {
|
||||
fixedDepositProduct.description = '';
|
||||
}
|
||||
const charts: any[] = [];
|
||||
fixedDepositProduct.charts.forEach((chart: any) => {
|
||||
if (chart['amountRangeFrom'] === '') {
|
||||
delete chart['amountRangeFrom'];
|
||||
}
|
||||
if (chart['amountRangeTo'] === '') {
|
||||
delete chart['amountRangeTo'];
|
||||
}
|
||||
charts.push(chart);
|
||||
});
|
||||
fixedDepositProduct.charts = charts;
|
||||
get selectedFdType(): string | null {
|
||||
return this.fixedDepositProductDetailsStep?.fixedDepositProductDetailsForm?.get('fdType')?.value ?? null;
|
||||
}
|
||||
|
||||
get activeBaseVersion() {
|
||||
const fdType = this.selectedFdType;
|
||||
const filtered = fdType ? this.fdInterestRates.filter((v: any) => v.fdType === fdType) : this.fdInterestRates;
|
||||
return filtered.find((v: any) => v.isActive) ?? filtered[0] ?? null;
|
||||
}
|
||||
|
||||
get activeBaseSlabs() {
|
||||
return this.activeBaseVersion?.slabs || [];
|
||||
}
|
||||
|
||||
get fixedDepositProductCharts() {
|
||||
const activeBaseVersion = this.activeBaseVersion;
|
||||
if (!activeBaseVersion?.slabs?.length) {
|
||||
return [];
|
||||
}
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const formattedFromDate =
|
||||
this.dateUtils.formatDate(activeBaseVersion.effectiveFromDate, dateFormat) || activeBaseVersion.effectiveFromDate;
|
||||
|
||||
// Find the existing chart from the template to UPDATE it instead of creating a duplicate
|
||||
let existingChartId: number | undefined;
|
||||
let existingChartSlabs: any[] = [];
|
||||
const templateCharts: any[] = this.fixedDepositProductsTemplate?.charts ?? [];
|
||||
if (templateCharts.length) {
|
||||
const match = templateCharts.find((c: any) => {
|
||||
const d = c.fromDate;
|
||||
if (Array.isArray(d)) {
|
||||
const formatted = this.dateUtils.formatDate(new Date(d[0], d[1] - 1, d[2]), dateFormat);
|
||||
return formatted === formattedFromDate;
|
||||
}
|
||||
return d === formattedFromDate;
|
||||
});
|
||||
const matchedChart = match ?? templateCharts[0];
|
||||
existingChartId = matchedChart?.id;
|
||||
existingChartSlabs = matchedChart?.chartSlabs ?? [];
|
||||
}
|
||||
if (!existingChartId && this.fixedDepositProductsTemplate?.activeChart?.id) {
|
||||
existingChartId = this.fixedDepositProductsTemplate.activeChart.id;
|
||||
existingChartSlabs = this.fixedDepositProductsTemplate.activeChart.chartSlabs ?? [];
|
||||
}
|
||||
|
||||
// Build a lookup: fromPeriod+toPeriod → existing slab id (to update existing slabs, not add new ones)
|
||||
const existingSlabIdMap = new Map<string, number>();
|
||||
for (const slab of existingChartSlabs) {
|
||||
const key = `${slab.fromPeriod ?? ''}_${slab.toPeriod ?? ''}`;
|
||||
if (slab.id) {
|
||||
existingSlabIdMap.set(key, slab.id);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
...(existingChartId ? { id: existingChartId } : {}),
|
||||
name: activeBaseVersion.versionLabel || 'CBS Base Chart',
|
||||
description: activeBaseVersion.remarks || activeBaseVersion.versionLabel || 'CBS base chart',
|
||||
fromDate: formattedFromDate,
|
||||
...(activeBaseVersion.effectiveToDate
|
||||
? {
|
||||
endDate:
|
||||
this.dateUtils.formatDate(activeBaseVersion.effectiveToDate, dateFormat) ||
|
||||
activeBaseVersion.effectiveToDate
|
||||
}
|
||||
: {}),
|
||||
isPrimaryGroupingByAmount: false,
|
||||
locale: this.settingsService.language.code,
|
||||
dateFormat,
|
||||
chartSlabs: activeBaseVersion.slabs.map((slab: any) => {
|
||||
const key = `${slab.fromPeriod ?? ''}_${slab.toPeriod ?? ''}`;
|
||||
const existingSlabId = existingSlabIdMap.get(key);
|
||||
|
||||
const chartSlab: any = {
|
||||
...(existingSlabId ? { id: existingSlabId } : {}),
|
||||
periodType: slab.periodTypeEnum,
|
||||
fromPeriod: slab.fromPeriod,
|
||||
toPeriod: slab.toPeriod,
|
||||
annualInterestRate: slab.annualInterestRate,
|
||||
description: slab.description || '',
|
||||
incentives: [] as any[]
|
||||
};
|
||||
|
||||
if (slab.amountRangeFrom !== null && slab.amountRangeFrom !== undefined && slab.amountRangeFrom !== '') {
|
||||
chartSlab.amountRangeFrom = slab.amountRangeFrom;
|
||||
}
|
||||
|
||||
if (slab.amountRangeTo !== null && slab.amountRangeTo !== undefined && slab.amountRangeTo !== '') {
|
||||
chartSlab.amountRangeTo = slab.amountRangeTo;
|
||||
}
|
||||
|
||||
return chartSlab;
|
||||
})
|
||||
}
|
||||
] as any[];
|
||||
}
|
||||
|
||||
submit() {
|
||||
const fixedDepositProduct: any = this.pruneEmptyValues({
|
||||
...this.fixedDepositProduct,
|
||||
charts: this.fixedDepositProductCharts,
|
||||
charges: (this.fixedDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code
|
||||
});
|
||||
delete fixedDepositProduct.advancedAccountingRules;
|
||||
this.productsService
|
||||
.updateFixedDepositProduct(this.fixedDepositProductsTemplate.id, fixedDepositProduct)
|
||||
@@ -169,4 +283,35 @@ export class EditFixedDepositProductComponent {
|
||||
this.router.navigate(['../'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
|
||||
private pruneEmptyValues<T>(value: T): T {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => this.pruneEmptyValues(item)).filter((item) => item !== undefined) as T;
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.entries(value as Record<string, any>).reduce(
|
||||
(acc, [
|
||||
key,
|
||||
currentValue
|
||||
]) => {
|
||||
const cleanedValue = this.pruneEmptyValues(currentValue);
|
||||
if (cleanedValue === undefined) {
|
||||
return acc;
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[key]: cleanedValue
|
||||
};
|
||||
},
|
||||
{} as Record<string, any>
|
||||
) as T;
|
||||
}
|
||||
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -36,7 +36,9 @@
|
||||
</div>
|
||||
<div class="fdpa-list">
|
||||
@for (row of section.rows; track row.key) {
|
||||
@if (!row.accrualOnly || isAccrualAccounting()) {
|
||||
@if (
|
||||
(!row.accrualOnly || isAccrualAccounting()) && (!row.dormancyOnly || isDormancyTrackingEnabled())
|
||||
) {
|
||||
<div class="fdpa-row">
|
||||
<div class="fdpa-row__meta">
|
||||
<span
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.fdpa-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
+131
-57
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, inject } from '@angular/core';
|
||||
import { Component, Input, OnChanges, OnInit, SimpleChanges, inject } from '@angular/core';
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
@@ -32,6 +32,7 @@ type AccountingRowConfig = {
|
||||
options: GLAccount[];
|
||||
required?: boolean;
|
||||
accrualOnly?: boolean;
|
||||
dormancyOnly?: boolean;
|
||||
};
|
||||
|
||||
type AccountingSectionConfig = {
|
||||
@@ -62,13 +63,14 @@ type OtherGlMappingValue = {
|
||||
GlAccountSelectorComponent
|
||||
]
|
||||
})
|
||||
export class FixedDepositProductAccountingStepComponent implements OnInit {
|
||||
export class FixedDepositProductAccountingStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private accounting = inject(Accounting);
|
||||
|
||||
@Input() fixedDepositProductsTemplate: any;
|
||||
@Input() accountingRuleData: any;
|
||||
@Input() fixedDepositProductFormValid: boolean;
|
||||
@Input() isDormancyTrackingActive: any;
|
||||
|
||||
fixedDepositProductAccountingForm: UntypedFormGroup;
|
||||
assetAccountOptions: GLAccount[] = [];
|
||||
@@ -83,6 +85,16 @@ export class FixedDepositProductAccountingStepComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.initializeAccountingForm();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.fixedDepositProductsTemplate?.currentValue) {
|
||||
this.initializeAccountingForm();
|
||||
}
|
||||
}
|
||||
|
||||
private initializeAccountingForm() {
|
||||
const accountingMappings = this.fixedDepositProductsTemplate?.accountingMappings || {};
|
||||
const mappings = {
|
||||
...accountingMappings,
|
||||
@@ -98,14 +110,25 @@ export class FixedDepositProductAccountingStepComponent implements OnInit {
|
||||
|
||||
this.buildAccountingSectionConfigs();
|
||||
|
||||
this.fixedDepositProductAccountingForm.patchValue({ accountingRule: accountingRuleId });
|
||||
this.fixedDepositProductAccountingForm.patchValue({ accountingRule: accountingRuleId }, { emitEvent: false });
|
||||
this.syncAccountingControls(accountingRuleId);
|
||||
this.patchAccountingValues(mappings);
|
||||
this.setDormancyControl();
|
||||
}
|
||||
|
||||
createFixedDepositProductAccountingForm() {
|
||||
this.fixedDepositProductAccountingForm = this.formBuilder.group({
|
||||
accountingRule: [2],
|
||||
savingsReferenceAccountId: [''],
|
||||
savingsControlAccountId: [''],
|
||||
transfersInSuspenseAccountId: [''],
|
||||
interestOnSavingsAccountId: [''],
|
||||
incomeFromFeeAccountId: [''],
|
||||
incomeFromPenaltyAccountId: [''],
|
||||
feesReceivableAccountId: [''],
|
||||
penaltiesReceivableAccountId: [''],
|
||||
interestPayableAccountId: [''],
|
||||
escheatLiabilityId: [''],
|
||||
assetsOtherGlMappings: this.formBuilder.array([]),
|
||||
liabilitiesOtherGlMappings: this.formBuilder.array([]),
|
||||
expensesOtherGlMappings: this.formBuilder.array([]),
|
||||
@@ -119,17 +142,54 @@ export class FixedDepositProductAccountingStepComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
private extractAccountId(mapping: any, ...keys: string[]) {
|
||||
for (const key of keys) {
|
||||
const value = mapping?.[key];
|
||||
if (value?.id != null) {
|
||||
return value.id;
|
||||
}
|
||||
if (value?.glAccountId != null) {
|
||||
return value.glAccountId;
|
||||
}
|
||||
if (typeof value === 'number' || typeof value === 'string') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
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 || ''
|
||||
savingsReferenceAccountId: this.extractAccountId(
|
||||
mappings,
|
||||
'savingsReferenceAccount',
|
||||
'savingsReferenceAccountId'
|
||||
),
|
||||
savingsControlAccountId: this.extractAccountId(mappings, 'savingsControlAccount', 'savingsControlAccountId'),
|
||||
transfersInSuspenseAccountId: this.extractAccountId(
|
||||
mappings,
|
||||
'transfersInSuspenseAccount',
|
||||
'transfersInSuspenseAccountId'
|
||||
),
|
||||
interestOnSavingsAccountId: this.extractAccountId(
|
||||
mappings,
|
||||
'interestOnSavingsAccount',
|
||||
'interestOnSavingsAccountId'
|
||||
),
|
||||
incomeFromFeeAccountId: this.extractAccountId(mappings, 'incomeFromFeeAccount', 'incomeFromFeeAccountId'),
|
||||
incomeFromPenaltyAccountId: this.extractAccountId(
|
||||
mappings,
|
||||
'incomeFromPenaltyAccount',
|
||||
'incomeFromPenaltyAccountId'
|
||||
),
|
||||
feesReceivableAccountId: this.extractAccountId(mappings, 'feeReceivableAccount', 'feesReceivableAccountId'),
|
||||
penaltiesReceivableAccountId: this.extractAccountId(
|
||||
mappings,
|
||||
'penaltyReceivableAccount',
|
||||
'penaltiesReceivableAccountId'
|
||||
),
|
||||
interestPayableAccountId: this.extractAccountId(mappings, 'interestPayableAccount', 'interestPayableAccountId'),
|
||||
escheatLiabilityId: this.extractAccountId(mappings, 'escheatLiabilityAccount', 'escheatLiabilityId')
|
||||
});
|
||||
|
||||
if (this.isCashOrAccrualAccounting()) {
|
||||
@@ -215,53 +275,43 @@ export class FixedDepositProductAccountingStepComponent implements OnInit {
|
||||
return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null;
|
||||
}
|
||||
|
||||
private setDormancyControl() {
|
||||
this.isDormancyTrackingActive?.valueChanges?.subscribe(() => {
|
||||
this.syncAccountingControls(this.fixedDepositProductAccountingForm.value.accountingRule);
|
||||
});
|
||||
}
|
||||
|
||||
isDormancyTrackingEnabled(): boolean {
|
||||
return this.isDormancyTrackingActive?.value ?? this.isDormancyTrackingActive ?? false;
|
||||
}
|
||||
|
||||
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);
|
||||
const setRequired = (name: string, required: boolean) => {
|
||||
const ctrl = this.fixedDepositProductAccountingForm.get(name);
|
||||
if (!ctrl) return;
|
||||
if (required) {
|
||||
ctrl.setValidators(Validators.required);
|
||||
} else {
|
||||
removeControl('feesReceivableAccountId');
|
||||
removeControl('penaltiesReceivableAccountId');
|
||||
removeControl('interestPayableAccountId');
|
||||
ctrl.clearValidators();
|
||||
}
|
||||
} else {
|
||||
[
|
||||
'savingsReferenceAccountId',
|
||||
'savingsControlAccountId',
|
||||
'transfersInSuspenseAccountId',
|
||||
'interestOnSavingsAccountId',
|
||||
'incomeFromFeeAccountId',
|
||||
'incomeFromPenaltyAccountId',
|
||||
'feesReceivableAccountId',
|
||||
'penaltiesReceivableAccountId',
|
||||
'interestPayableAccountId'
|
||||
].forEach(removeControl);
|
||||
ctrl.updateValueAndValidity({ emitEvent: false });
|
||||
};
|
||||
|
||||
const cashOrAccrual = this.accounting.isCashOrAccrualAccountingRuleId(accountingRule);
|
||||
const accrualOnly = this.accounting.isAccrualAccountingRuleId(accountingRule);
|
||||
|
||||
setRequired('savingsReferenceAccountId', cashOrAccrual);
|
||||
setRequired('savingsControlAccountId', cashOrAccrual);
|
||||
setRequired('transfersInSuspenseAccountId', cashOrAccrual);
|
||||
setRequired('interestOnSavingsAccountId', cashOrAccrual);
|
||||
setRequired('incomeFromFeeAccountId', cashOrAccrual);
|
||||
setRequired('incomeFromPenaltyAccountId', cashOrAccrual);
|
||||
setRequired('feesReceivableAccountId', accrualOnly);
|
||||
setRequired('penaltiesReceivableAccountId', accrualOnly);
|
||||
setRequired('interestPayableAccountId', accrualOnly);
|
||||
setRequired('escheatLiabilityId', cashOrAccrual && this.isDormancyTrackingEnabled());
|
||||
|
||||
if (!cashOrAccrual) {
|
||||
this.clearOtherGlMappings();
|
||||
}
|
||||
}
|
||||
@@ -331,6 +381,14 @@ export class FixedDepositProductAccountingStepComponent implements OnInit {
|
||||
controlName: 'interestPayableAccountId',
|
||||
options: this.liabilityAccountOptions,
|
||||
accrualOnly: true
|
||||
},
|
||||
{
|
||||
key: 'escheatLiabilityId',
|
||||
label: 'Escheat Liability Account',
|
||||
inputLabel: 'Escheat liability account',
|
||||
controlName: 'escheatLiabilityId',
|
||||
options: this.liabilityAccountOptions,
|
||||
dormancyOnly: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'liabilitiesOtherGlMappings',
|
||||
@@ -392,7 +450,23 @@ export class FixedDepositProductAccountingStepComponent implements OnInit {
|
||||
...accounting
|
||||
} = this.fixedDepositProductAccountingForm.value;
|
||||
const otherGlMappings = this.getOtherGlMappings();
|
||||
return { ...accounting, ...(otherGlMappings ? { otherGlMappings } : {}) };
|
||||
const cleanedAccounting = Object.entries(accounting).reduce(
|
||||
(acc, [
|
||||
key,
|
||||
value
|
||||
]) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
return acc;
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[key]: value
|
||||
};
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
|
||||
return { ...cleanedAccounting, ...(otherGlMappings ? { otherGlMappings } : {}) };
|
||||
}
|
||||
|
||||
isCashOrAccrualAccounting(): boolean {
|
||||
|
||||
+22
-19
@@ -63,27 +63,30 @@
|
||||
</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>
|
||||
@if (fixedDepositProductCurrencyForm.get('dormancyAlert')?.value === true) {
|
||||
<div
|
||||
class="fdpc-grid"
|
||||
style="border-top: 1px solid #e2e8f0; background: #f8fafc; padding-top: 24px; padding-bottom: 24px"
|
||||
>
|
||||
<mat-form-field appearance="outline" class="fdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Days to Inactive' | translate }}</mat-label>
|
||||
<input matInput type="number" formControlName="daysToInactive" required />
|
||||
<mat-error> {{ 'labels.inputs.Days to Inactive' | translate }} is <strong>required</strong> </mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="fdpc-alert">
|
||||
<div class="fdpc-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Micro, Small and Medium Enterprises</p>
|
||||
<mat-form-field appearance="outline" class="fdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Days to Dormancy' | translate }}</mat-label>
|
||||
<input matInput type="number" formControlName="daysToDormancy" required />
|
||||
<mat-error> {{ 'labels.inputs.Days to Dormancy' | translate }} is <strong>required</strong> </mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Days to Escheat' | translate }}</mat-label>
|
||||
<input matInput type="number" formControlName="daysToEscheat" required />
|
||||
<mat-error> {{ 'labels.inputs.Days to Escheat' | translate }} is <strong>required</strong> </mat-error>
|
||||
</mat-form-field>
|
||||
</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">
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.fdpc-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
+18
-7
@@ -51,8 +51,10 @@ export class FixedDepositProductCurrencyStepComponent implements OnInit {
|
||||
this.fixedDepositProductsTemplate.currency?.decimalPlaces ??
|
||||
2,
|
||||
dormancyAlert: this.fixedDepositProductsTemplate.dormancyAlert ?? false,
|
||||
balanceSwapOption: this.fixedDepositProductsTemplate.balanceSwapOption ?? false,
|
||||
msme: this.fixedDepositProductsTemplate.msme ?? false,
|
||||
isDormancyTrackingActive: this.fixedDepositProductsTemplate.isDormancyTrackingActive ?? false,
|
||||
daysToInactive: this.fixedDepositProductsTemplate.daysToInactive ?? 30,
|
||||
daysToDormancy: this.fixedDepositProductsTemplate.daysToDormancy ?? 60,
|
||||
daysToEscheat: this.fixedDepositProductsTemplate.daysToEscheat ?? 90,
|
||||
nominationFacility: this.fixedDepositProductsTemplate.nominationFacility ?? false,
|
||||
autoDebitFacility: this.fixedDepositProductsTemplate.autoDebitFacility ?? false
|
||||
});
|
||||
@@ -72,8 +74,10 @@ export class FixedDepositProductCurrencyStepComponent implements OnInit {
|
||||
]
|
||||
],
|
||||
dormancyAlert: [false],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
isDormancyTrackingActive: [false],
|
||||
daysToInactive: [30],
|
||||
daysToDormancy: [60],
|
||||
daysToEscheat: [90],
|
||||
nominationFacility: [false],
|
||||
autoDebitFacility: [false]
|
||||
});
|
||||
@@ -81,14 +85,21 @@ export class FixedDepositProductCurrencyStepComponent implements OnInit {
|
||||
|
||||
get fixedDepositProductCurrency() {
|
||||
const formValue = this.fixedDepositProductCurrencyForm.value;
|
||||
return {
|
||||
const result: any = {
|
||||
currencyCode: formValue.currencyCode,
|
||||
digitsAfterDecimal: formValue.digitsAfterDecimal,
|
||||
dormancyAlert: formValue.dormancyAlert,
|
||||
balanceSwapOption: formValue.balanceSwapOption,
|
||||
msme: formValue.msme,
|
||||
nominationFacility: formValue.nominationFacility,
|
||||
autoDebitFacility: formValue.autoDebitFacility
|
||||
};
|
||||
if (formValue.dormancyAlert) {
|
||||
result.isDormancyTrackingActive = true;
|
||||
result.daysToInactive = formValue.daysToInactive;
|
||||
result.daysToDormancy = formValue.daysToDormancy;
|
||||
result.daysToEscheat = formValue.daysToEscheat;
|
||||
} else {
|
||||
result.isDormancyTrackingActive = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-14
@@ -79,12 +79,13 @@
|
||||
</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-label>FD Type</mat-label>
|
||||
<mat-select formControlName="fdType" required>
|
||||
<mat-option value="SINGLE">FD Single (Days < 1 Year)</mat-option>
|
||||
<mat-option value="DOUBLE">FD Double (≥ 1 Year)</mat-option>
|
||||
<mat-option value="SPECIAL">FD Special (Monthly Interest Payout)</mat-option>
|
||||
</mat-select>
|
||||
<mat-error>FD Type <strong>is required</strong></mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpd-field">
|
||||
@@ -110,15 +111,6 @@
|
||||
<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>
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.fdpd-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
+10
-10
@@ -34,12 +34,12 @@ export class FixedDepositProductDetailsStepComponent implements OnInit, OnChange
|
||||
|
||||
fixedDepositProductDetailsForm: UntypedFormGroup;
|
||||
accountTypeData: string[] = [];
|
||||
customerSegmentData: string[] = [];
|
||||
liabilityAccountOptions: any[] = [];
|
||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
||||
{ value: true, label: 'Yes' },
|
||||
{ value: false, label: 'No' }
|
||||
fdTypeOptions: string[] = [
|
||||
'SINGLE',
|
||||
'DOUBLE',
|
||||
'SPECIAL'
|
||||
];
|
||||
liabilityAccountOptions: any[] = [];
|
||||
|
||||
constructor() {
|
||||
this.createFixedDepositProductDetailsForm();
|
||||
@@ -72,11 +72,13 @@ export class FixedDepositProductDetailsStepComponent implements OnInit, OnChange
|
||||
null,
|
||||
Validators.required
|
||||
],
|
||||
customerSegment: [null],
|
||||
fdType: [
|
||||
null,
|
||||
Validators.required
|
||||
],
|
||||
glAccountId: [null],
|
||||
effectiveTo: [''],
|
||||
endDate: [''],
|
||||
introducerRequired: [false],
|
||||
shareLinkingEnabled: [false]
|
||||
});
|
||||
}
|
||||
@@ -99,7 +101,6 @@ export class FixedDepositProductDetailsStepComponent implements OnInit, OnChange
|
||||
|
||||
private applyTemplate(template: any) {
|
||||
this.accountTypeData = this.getOptionNames(template.accountTypeOptions);
|
||||
this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions);
|
||||
this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
this.cdr.detectChanges();
|
||||
|
||||
@@ -109,11 +110,10 @@ export class FixedDepositProductDetailsStepComponent implements OnInit, OnChange
|
||||
shortName: isEditing ? template.shortName : template.shortName || '',
|
||||
description: template.description || '',
|
||||
accountType: template.accountType ?? this.accountTypeData[0] ?? null,
|
||||
customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null,
|
||||
fdType: template.fdType ?? 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
|
||||
});
|
||||
}
|
||||
|
||||
+639
-563
File diff suppressed because it is too large
Load Diff
+109
-98
@@ -6,103 +6,39 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
.fdpp-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
@use 'assets/styles/colours' as *;
|
||||
|
||||
.mat-h3 {
|
||||
margin-top: 10px !important;
|
||||
font-weight: 300;
|
||||
color: $status-approved;
|
||||
}
|
||||
|
||||
.fdpp-title {
|
||||
.mat-elevation-z1 {
|
||||
margin: 1em 0 1.5em;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
}
|
||||
|
||||
.fdpp-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.fdpp-section-body {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.fdpp-preview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpp-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px 0;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpp-label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #5f6368;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.fdpp-value {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #202124;
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.fdpp-chart-block {
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #e2e8f0;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
span {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
mat-divider {
|
||||
margin: 0 0 0.5em;
|
||||
}
|
||||
|
||||
/* ── Incentives expand row ── */
|
||||
.fdpp-table-wrapper {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
margin-top: 8px;
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -132,20 +68,95 @@
|
||||
}
|
||||
}
|
||||
|
||||
.fdpp-subsection-title {
|
||||
margin: 16px 0 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
/* ── GL mapping summary (same as RD product preview) ── */
|
||||
.spa-gl-summary,
|
||||
.spa-other-gl-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.9rem;
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
.spa-gl-summary__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.25rem 0 0.5rem;
|
||||
}
|
||||
|
||||
.spa-gl-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem 1.15rem;
|
||||
border: 1px solid #e3e7f2;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
|
||||
h5 {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #91a0bc;
|
||||
}
|
||||
}
|
||||
|
||||
.fdpp-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 16px;
|
||||
.spa-gl-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(220px, 40%) 1fr;
|
||||
gap: 1rem;
|
||||
align-items: baseline;
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
|
||||
.spa-gl-row__label {
|
||||
color: #1f2f4a;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.spa-gl-row__value {
|
||||
color: #101828;
|
||||
font-weight: 600;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.spa-other-gl-table {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
th.mat-mdc-header-cell,
|
||||
td.mat-mdc-cell {
|
||||
padding-left: 1.25rem;
|
||||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
th.mat-mdc-header-cell:first-child,
|
||||
td.mat-mdc-cell:first-child {
|
||||
width: 38%;
|
||||
}
|
||||
|
||||
th.mat-mdc-header-cell:last-child,
|
||||
td.mat-mdc-cell:last-child {
|
||||
width: 62%;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.spa-other-gl-summary .mat-elevation-z1 {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (width <= 768px) {
|
||||
.spa-gl-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
}
|
||||
|
||||
+48
-1
@@ -10,6 +10,7 @@ import { Component, OnInit, Input, Output, EventEmitter, SimpleChanges, OnChange
|
||||
import { trigger, state, transition, animate, style } from '@angular/animations';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { OptionData } from 'app/shared/models/option-data.model';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
|
||||
import {
|
||||
MatTable,
|
||||
@@ -44,6 +45,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDivider,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
@@ -100,6 +102,23 @@ export class FixedDepositProductPreviewStepComponent implements OnInit, OnChange
|
||||
accountingMappings: any = {};
|
||||
accountingRule: OptionData;
|
||||
|
||||
readonly fdSlabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
'fromPeriod',
|
||||
'toPeriod',
|
||||
'periodTypeLabel',
|
||||
'annualInterestRate'
|
||||
];
|
||||
|
||||
get otherGlMappings() {
|
||||
return this.fixedDepositProduct?.otherGlMappings || this.fixedDepositProduct?.otherGlMappingsMap || null;
|
||||
}
|
||||
|
||||
get fdSlabs(): any[] {
|
||||
return this.fixedDepositProduct?.fdSlabs ?? [];
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.setCurrentValues();
|
||||
}
|
||||
@@ -142,7 +161,9 @@ export class FixedDepositProductPreviewStepComponent implements OnInit, OnChange
|
||||
incomeFromPenaltyAccount: this.accounting.glAccountLookUp(
|
||||
this.fixedDepositProduct.incomeFromPenaltyAccountId,
|
||||
incomeAccountData
|
||||
)
|
||||
),
|
||||
escheatLiabilityAccount:
|
||||
this.accounting.glAccountLookUp(this.fixedDepositProduct.escheatLiabilityId, liabilityAccountData) || null
|
||||
};
|
||||
|
||||
if (this.isAccrualAccounting()) {
|
||||
@@ -173,4 +194,30 @@ export class FixedDepositProductPreviewStepComponent implements OnInit, OnChange
|
||||
isAccrualAccounting(): boolean {
|
||||
return this.accounting.isAccrualAccountingRuleId(this.fixedDepositProduct.accountingRule);
|
||||
}
|
||||
|
||||
getLockinPeriodFrequencyTypeLabel(value: any): string {
|
||||
if (!value && value !== 0) return '';
|
||||
const options = this.fixedDepositProductsTemplate?.lockinPeriodFrequencyTypeOptions || [];
|
||||
const found = options.find((o: any) => o.id === value);
|
||||
return found?.value || String(value);
|
||||
}
|
||||
|
||||
getGlAccountName(id: any): string {
|
||||
if (!id) return '';
|
||||
const accounts = this.fixedDepositProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
const found = accounts.find((a: any) => a.id === id || a.id === +id);
|
||||
return found ? `${found.name} (${found.glCode})` : String(id);
|
||||
}
|
||||
|
||||
formatGlAccountById(glAccountId: number, options: any[]): string {
|
||||
if (!glAccountId || !Array.isArray(options)) return '-';
|
||||
const account = this.accounting.glAccountLookUp(glAccountId, options as any);
|
||||
return account ? `(${account.glCode}) ${account.name}` : '-';
|
||||
}
|
||||
|
||||
formatGlAccountByCode(glCode: string, options: any[]): string {
|
||||
if (!glCode || !Array.isArray(options)) return glCode || '-';
|
||||
const account = options.find((o: any) => o?.glCode === glCode);
|
||||
return account ? `(${account.glCode}) ${account.name}` : glCode;
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.fdps-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
+50
-1
@@ -7,6 +7,33 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="fixedDepositProductTermsForm">
|
||||
<input type="hidden" formControlName="minDepositTerm" />
|
||||
<input type="hidden" formControlName="minDepositTermTypeId" />
|
||||
<input type="hidden" formControlName="maxDepositTerm" />
|
||||
<input type="hidden" formControlName="maxDepositTermTypeId" />
|
||||
<input type="hidden" formControlName="inMultiplesOfDepositTerm" />
|
||||
<input type="hidden" formControlName="inMultiplesOfDepositTermTypeId" />
|
||||
|
||||
<div class="fdpt-section">
|
||||
<div class="fdpt-section__header">
|
||||
<span class="fdpt-section__badge"><i class="fas fa-coins"></i></span>
|
||||
<span>Deposit Amount</span>
|
||||
</div>
|
||||
<div class="fdpt-body">
|
||||
<div class="fdpt-grid fdpt-grid--three">
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Minimum</mat-label>
|
||||
<input type="number" matInput formControlName="minDepositAmount" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Maximum</mat-label>
|
||||
<input type="number" matInput formControlName="maxDepositAmount" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interest Rate & Posting Terms -->
|
||||
<div class="fdpt-section">
|
||||
<div class="fdpt-section__header">
|
||||
@@ -65,6 +92,28 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="fdpt-grid fdpt-grid--three">
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Interest Compounding Period</mat-label>
|
||||
<mat-select formControlName="interestCompoundingPeriodType" required>
|
||||
@for (opt of interestCompoundingPeriodTypeData; track opt.id) {
|
||||
<mat-option [value]="opt.id">{{ opt.value | translateKey: 'catalogs' }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Interest Compounding Period <strong>is required</strong></mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="fdpt-field">
|
||||
<mat-label>Days in Year</mat-label>
|
||||
<mat-select formControlName="interestCalculationDaysInYearType" required>
|
||||
@for (opt of interestCalculationDaysInYearTypeData; track opt.id) {
|
||||
<mat-option [value]="opt.id">{{ opt.value | translateKey: 'catalogs' }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Days in Year <strong>is required</strong></mat-error>
|
||||
</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>
|
||||
@@ -83,7 +132,7 @@
|
||||
<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) {
|
||||
@for (period of averagePeriodData; track period.id) {
|
||||
<mat-option [value]="period.value">{{ period.value }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.fdpt-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
+69
-22
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||
import { Component, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
@@ -22,12 +22,13 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatTooltip
|
||||
]
|
||||
})
|
||||
export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
export class FixedDepositProductTermsStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
@Input() fixedDepositProductsTemplate: any;
|
||||
@Input() fdType: string | null = null;
|
||||
|
||||
fixedDepositProductTermsForm: UntypedFormGroup;
|
||||
|
||||
@@ -36,6 +37,7 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
interestCalculationTypeData: any;
|
||||
interestCalculationTypeOptionData: any;
|
||||
interestCalculationDaysInYearTypeData: any;
|
||||
averagePeriodData: any[] = [];
|
||||
periodFrequencyData: any[] = [];
|
||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
||||
{ value: true, label: 'Yes' },
|
||||
@@ -47,16 +49,33 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.initializeTerms();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.fdType) {
|
||||
this.applyFdTypeConstraints();
|
||||
}
|
||||
if (changes.fixedDepositProductsTemplate?.currentValue) {
|
||||
this.initializeTerms();
|
||||
}
|
||||
}
|
||||
|
||||
private initializeTerms() {
|
||||
if (!this.fixedDepositProductsTemplate) return;
|
||||
|
||||
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 || [];
|
||||
this.averagePeriodData = this.fixedDepositProductsTemplate.periodFrequencyOptions || [];
|
||||
this.periodFrequencyData = this.fixedDepositProductsTemplate.periodFrequencyTypeOptions || [];
|
||||
|
||||
const isEditing = !!this.fixedDepositProductsTemplate.id;
|
||||
const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly';
|
||||
const defaultPeriodTypeId = this.periodFrequencyData[0]?.id ?? null;
|
||||
const defaultAveragePeriod = this.averagePeriodData[0]?.value || 'Monthly';
|
||||
const savedEnumTypeId =
|
||||
this.fixedDepositProductsTemplate.interestCalculationType?.id || this.interestCalculationTypeData?.[0]?.id || 1;
|
||||
const savedSelectorId = isEditing
|
||||
@@ -65,15 +84,15 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
null)
|
||||
: (this.interestCalculationTypeOptionData?.[0]?.id ?? null);
|
||||
|
||||
if (isEditing) {
|
||||
this.fixedDepositProductTermsForm.patchValue({
|
||||
minDepositAmount: this.fixedDepositProductsTemplate.minDepositAmount,
|
||||
depositAmount: this.fixedDepositProductsTemplate.depositAmount,
|
||||
maxDepositAmount: this.fixedDepositProductsTemplate.maxDepositAmount
|
||||
});
|
||||
}
|
||||
|
||||
this.fixedDepositProductTermsForm.patchValue({
|
||||
minDepositTerm: this.fixedDepositProductsTemplate.minDepositTerm ?? 1,
|
||||
minDepositTermTypeId: this.fixedDepositProductsTemplate.minDepositTermType?.id ?? defaultPeriodTypeId,
|
||||
maxDepositTerm: this.fixedDepositProductsTemplate.maxDepositTerm ?? '',
|
||||
maxDepositTermTypeId: this.fixedDepositProductsTemplate.maxDepositTermType?.id ?? '',
|
||||
inMultiplesOfDepositTerm: this.fixedDepositProductsTemplate.inMultiplesOfDepositTerm ?? '',
|
||||
inMultiplesOfDepositTermTypeId: this.fixedDepositProductsTemplate.inMultiplesOfDepositTermType?.id ?? '',
|
||||
minDepositAmount: this.fixedDepositProductsTemplate.minDepositAmount,
|
||||
maxDepositAmount: this.fixedDepositProductsTemplate.maxDepositAmount,
|
||||
nominalAnnualInterestRate: this.fixedDepositProductsTemplate.nominalAnnualInterestRate ?? 0,
|
||||
interestCompoundingPeriodType: this.fixedDepositProductsTemplate.interestCompoundingPeriodType?.id,
|
||||
interestPostingPeriodType: this.fixedDepositProductsTemplate.interestPostingPeriodType?.id,
|
||||
@@ -83,13 +102,34 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
? this.parseDate(this.fixedDepositProductsTemplate.effectiveDate)
|
||||
: '',
|
||||
interestCalculateSelector: savedSelectorId,
|
||||
averagePeriod: isEditing ? this.fixedDepositProductsTemplate.averagePeriod || defaultPeriod : defaultPeriod,
|
||||
averagePeriod: isEditing
|
||||
? this.fixedDepositProductsTemplate.averagePeriod || defaultAveragePeriod
|
||||
: defaultAveragePeriod,
|
||||
autoPost: isEditing ? (this.fixedDepositProductsTemplate.autoPost ?? true) : true,
|
||||
frequencyFixDay: isEditing ? (this.fixedDepositProductsTemplate.frequencyFixDay ?? 30) : 30,
|
||||
lastInterestDate: this.fixedDepositProductsTemplate.lastInterestDate
|
||||
? this.parseDate(this.fixedDepositProductsTemplate.lastInterestDate)
|
||||
: ''
|
||||
});
|
||||
this.applyFdTypeConstraints();
|
||||
}
|
||||
|
||||
private applyFdTypeConstraints() {
|
||||
if (!this.fixedDepositProductTermsForm || !this.interestPostingPeriodTypeData) return;
|
||||
const postingCtrl = this.fixedDepositProductTermsForm.get('interestPostingPeriodType');
|
||||
if (!postingCtrl) return;
|
||||
if (this.fdType === 'SPECIAL') {
|
||||
// Monthly posting = id 4 (MONTHLY in Fineract enum)
|
||||
const monthlyOption = this.interestPostingPeriodTypeData?.find(
|
||||
(o: any) => o.value?.toLowerCase() === 'monthly' || o.id === 4
|
||||
);
|
||||
if (monthlyOption) {
|
||||
postingCtrl.setValue(monthlyOption.id);
|
||||
postingCtrl.disable();
|
||||
}
|
||||
} else {
|
||||
postingCtrl.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private parseDate(dateValue: any): Date | null {
|
||||
@@ -101,18 +141,21 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
|
||||
createFixedDepositProductTermsForm() {
|
||||
this.fixedDepositProductTermsForm = this.formBuilder.group({
|
||||
minDepositAmount: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
depositAmount: [
|
||||
'',
|
||||
minDepositTerm: [
|
||||
1,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
maxDepositAmount: [
|
||||
minDepositTermTypeId: [''],
|
||||
maxDepositTerm: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
maxDepositTermTypeId: [''],
|
||||
inMultiplesOfDepositTerm: [''],
|
||||
inMultiplesOfDepositTermTypeId: [''],
|
||||
minDepositAmount: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
@@ -139,6 +182,10 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
maxDepositAmount: [
|
||||
'',
|
||||
Validators.min(0)
|
||||
],
|
||||
effectiveDate: [''],
|
||||
interestCalculateSelector: [1],
|
||||
averagePeriod: ['Monthly'],
|
||||
@@ -152,7 +199,7 @@ export class FixedDepositProductTermsStepComponent implements OnInit {
|
||||
}
|
||||
|
||||
get fixedDepositProductTerms() {
|
||||
const formValue = { ...this.fixedDepositProductTermsForm.value };
|
||||
const formValue = { ...this.fixedDepositProductTermsForm.getRawValue() };
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
if (formValue.effectiveDate instanceof Date) {
|
||||
|
||||
+323
-388
@@ -6,23 +6,29 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0.
|
||||
-->
|
||||
|
||||
<div class="layout-row align-end gap-2px responsive-column container m-t-20 m-b-20">
|
||||
<button mat-raised-button color="primary" [routerLink]="['../edit']" *mifosxHasPermission="'UPDATE_SAVINGSPRODUCT'">
|
||||
<fa-icon icon="edit" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Edit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<div class="responsive-column">
|
||||
<h2 class="mat-h2 flex-100">{{ fixedDepositProductData.name }}</h2>
|
||||
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
|
||||
<!-- Details -->
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Details' | translate }}</h3>
|
||||
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Short Name' | translate }}:</span>
|
||||
@@ -36,9 +42,59 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||
<!-- Product Classification -->
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">Product Classification</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
@if (fixedDepositProductData.accountType) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Account Type' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.accountType }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.fdType) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.FD Type' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.fdType }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.glAccountName || fixedDepositProductData.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
<span class="flex-60">
|
||||
{{ fixedDepositProductData.glAccountName || fixedDepositProductData.glAccountId }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.effectiveTo) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Effective To:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.effectiveTo | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.endDate) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.End Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.shareLinkingEnabled !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Share Linking Enabled' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.shareLinkingEnabled | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Currency -->
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.heading.Currency' | translate }}:</span>
|
||||
@@ -57,83 +113,229 @@
|
||||
<span class="flex-60">{{ fixedDepositProductData.currency.inMultiplesOf | formatNumber }}</span>
|
||||
</div>
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Terms' | translate }}</h3>
|
||||
@if (fixedDepositProductData.dormancyAlert !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Dormancy Alert:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.dormancyAlert | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
@if (fixedDepositProductData.dormancyAlert) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Days to Inactive:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.daysToInactive }}</span>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Days to Dormancy:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.daysToDormancy }}</span>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Days to Escheat:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.daysToEscheat }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.nominationFacility !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Nomination Facility' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.nominationFacility | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.autoDebitFacility !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Auto Debit Facility' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.autoDebitFacility | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Deposit Amount -->
|
||||
@if (
|
||||
fixedDepositProductData.minDepositAmount !== undefined ||
|
||||
fixedDepositProductData.maxDepositAmount !== undefined
|
||||
) {
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">Deposit Amount</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
@if (fixedDepositProductData.minDepositAmount !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Minimum:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.minDepositAmount }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositProductData.maxDepositAmount !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Maximum:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.maxDepositAmount }}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- Terms -->
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Terms' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Deposit Amount' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
fixedDepositProductData.depositAmount +
|
||||
' (Min: ' +
|
||||
(fixedDepositProductData.minDepositAmount
|
||||
? fixedDepositProductData.minDepositAmount
|
||||
: fixedDepositProductData.depositAmount) +
|
||||
', Max: ' +
|
||||
(fixedDepositProductData.maxDepositAmount
|
||||
? fixedDepositProductData.maxDepositAmount
|
||||
: fixedDepositProductData.depositAmount) +
|
||||
')'
|
||||
}}</span>
|
||||
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest Rate' | translate }} (%):</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.nominalAnnualInterestRate | formatNumber }} %</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestCompoundingPeriodType.value }}</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestCompoundingPeriodType?.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestPostingPeriodType.value }}</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestPostingPeriodType?.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Interest Calculated Using' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestCalculationType.value }}</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestCalculationType?.value }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestCalculationDaysInYearType.value }}</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestCalculationDaysInYearType?.value }}</span>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductData.interestCalculateSelector) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Interest Calculation Selector:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.interestCalculateSelector }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.averagePeriod) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Average Period:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.averagePeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Auto Interest Post:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.autoPost | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductData.frequencyFixDay !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Frequency (Fix Day):</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.frequencyFixDay }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.lastInterestDate) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Last Interest Date:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.lastInterestDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Settings -->
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Settings' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Loan Facility' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.loanFacility | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductData.lockinPeriodFrequency) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Premature Withdrawal Option' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.prematureWithdrawalOption | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Tenure Flexibility' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.tenureFlexibility | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Lien Marked' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.lienMarked | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.withdrawalFeeForTransfers | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Enable Lock-in Period:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.enableLockinPeriod | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductData.lockinPeriodFrequency !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Lock-in Period' | translate }}:</span>
|
||||
<span class="flex-60">
|
||||
{{ fixedDepositProductData.lockinPeriodFrequency }}
|
||||
{{ fixedDepositProductData.lockinPeriodFrequencyType.value }}</span
|
||||
>
|
||||
{{ fixedDepositProductData.lockinPeriodFrequencyType?.value }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.minDepositTerm) {
|
||||
@if (fixedDepositProductData.maturityInstructions !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Maturity Instructions:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.maturityInstructions | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Credit Maturity Proceeds:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.creditMaturityProceeds | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Penalty Grace Period (Missed Installments):</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.penaltyGracePeriodMissedInstallments | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Panel Request Applicable:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.panelRequestApplicable | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">No Penalty (Nominee / Heir Closure):</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.noPenaltyNomineeHeirClosure | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Deceased Settlement:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.deceasedSettlement | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Allow Debit Txn After Due Date:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.allowDebitTxnAfterDueDate | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Account Closed with Zero Balance in Day Closing:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.accountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (fixedDepositProductData.minBalanceForInterestCalculation !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Min Balance for Interest Calculation:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.minBalanceForInterestCalculation | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.minDepositTerm !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Minimum Deposit Term' | translate }}:</span>
|
||||
@if (fixedDepositProductData.minDepositTerm !== undefined) {
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositProductData.minDepositTerm }}
|
||||
|
||||
{{ fixedDepositProductData.minDepositTermType.value }}</span
|
||||
>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.maxDepositTerm) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Maximum Deposit Term' | translate }}:</span>
|
||||
@if (fixedDepositProductData.maxDepositTerm !== undefined) {
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositProductData.maxDepositTerm }}
|
||||
|
||||
{{ fixedDepositProductData.maxDepositTermType.value }}</span
|
||||
>
|
||||
}
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositProductData.minDepositTerm }}
|
||||
{{ fixedDepositProductData.minDepositTermType?.value }}</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -142,34 +344,35 @@
|
||||
<span class="flex-40">{{ 'labels.inputs.Deposit Term In multiple of' | translate }}:</span>
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositProductData.inMultiplesOfDepositTerm }}
|
||||
-
|
||||
{{ fixedDepositProductData.inMultiplesOfDepositTermType.value }}</span
|
||||
{{ fixedDepositProductData.inMultiplesOfDepositTermType?.value }}</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.preClosurePenalApplicable | yesNo }}</span>
|
||||
</div>
|
||||
@if (fixedDepositProductData.maxDepositTerm) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Maximum Deposit Term' | translate }}:</span>
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositProductData.maxDepositTerm }}
|
||||
{{ fixedDepositProductData.maxDepositTermType?.value }}</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.preClosurePenalApplicable) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.preClosurePenalApplicable | yesNo }}</span>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Penal Interest' | translate }} (%):</span>
|
||||
<span class="flex-60"
|
||||
>{{ fixedDepositProductData.preClosurePenalInterest }} % On
|
||||
{{ fixedDepositProductData.preClosurePenalInterestOnType.value }}</span
|
||||
>{{ fixedDepositProductData.preClosurePenalInterest }} % On
|
||||
{{ fixedDepositProductData.preClosurePenalInterestOnType?.value }}</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.minBalanceForInterestCalculation) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.minBalanceForInterestCalculation | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Withhold Tax is Applicable' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.withHoldTax | yesNo }}</span>
|
||||
@@ -178,249 +381,72 @@
|
||||
@if (fixedDepositProductData.withHoldTax) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Withhold Tax group' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.taxGroup.name }}</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.taxGroup?.name }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (fixedDepositProductData.activeChart) {
|
||||
<div class="responsive-column flex-100">
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Interest Rate Chart' | translate }}</h3>
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
<div class="responsive-column flex-100">
|
||||
@if (fixedDepositProductData.activeChart.name) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Name' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.activeChart.name }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositProductData.activeChart.description) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.activeChart.description }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.From Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.activeChart.fromDate | dateFormat }}</span>
|
||||
</div>
|
||||
@if (fixedDepositProductData.activeChart.endDate) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.End Date' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.activeChart.endDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}:</span>
|
||||
<span class="flex-60">{{ fixedDepositProductData.activeChart.isPrimaryGroupingByAmount | yesNo }}</span>
|
||||
</div>
|
||||
<table
|
||||
class="mat-elevation-z1 flex-100"
|
||||
mat-table
|
||||
[dataSource]="fixedDepositProductData.activeChart.chartSlabs"
|
||||
multiTemplateDataRows
|
||||
>
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
@if (chartSlab.fromPeriod !== undefined) {
|
||||
<div>{{ chartSlab.fromPeriod }} - {{ chartSlab.toPeriod }} {{ chartSlab.periodType.value }}</div>
|
||||
}
|
||||
@if (chartSlab.fromPeriod === undefined) {
|
||||
<div> </div>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amountRange">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
@if (chartSlab.amountRangeFrom !== undefined) {
|
||||
<div>{{ chartSlab.amountRangeFrom }} - {{ chartSlab.amountRangeTo }}</div>
|
||||
}
|
||||
@if (chartSlab.amountRangeFrom === undefined) {
|
||||
<div> </div>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="annualInterestRate">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate }} %</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.description }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
||||
>
|
||||
@if (expandChartSlabIndex !== chartSlabIndex) {
|
||||
<span>
|
||||
<fa-icon icon="eye" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.View Incentives' | translate }}
|
||||
</span>
|
||||
}
|
||||
@if (expandChartSlabIndex === chartSlabIndex) {
|
||||
<span>
|
||||
<fa-icon icon="eye-slash" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Hide Incentives' | translate }}
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
||||
>
|
||||
<div
|
||||
class="incentives flex-100 layout-row-wrap"
|
||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||
>
|
||||
<mat-card class="layout-row-wrap flex-100">
|
||||
<h4 class="mat-h4 flex-23">
|
||||
{{ 'labels.heading.Incentives' | translate }}
|
||||
</h4>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="chartSlab.incentives">
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{
|
||||
incentive.entityType
|
||||
| find: fixedDepositProductsTemplate.chartTemplate.entityTypeOptions : 'id' : 'value'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{
|
||||
incentive.attributeName
|
||||
| find
|
||||
: fixedDepositProductsTemplate.chartTemplate.attributeNameOptions
|
||||
: 'id'
|
||||
: 'value'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{
|
||||
incentive.conditionType
|
||||
| find
|
||||
: fixedDepositProductsTemplate.chartTemplate.conditionTypeOptions
|
||||
: 'id'
|
||||
: 'value'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
@switch (incentive.attributeName) {
|
||||
@case (2) {
|
||||
<span>{{
|
||||
incentive.attributeValue
|
||||
| find: fixedDepositProductsTemplate.chartTemplate.genderOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
}
|
||||
@case (3) {
|
||||
<span>{{ incentive.attributeValue }}</span>
|
||||
}
|
||||
@case (4) {
|
||||
<span>{{
|
||||
incentive.attributeValue
|
||||
| find
|
||||
: fixedDepositProductsTemplate.chartTemplate.clientTypeOptions
|
||||
: 'id'
|
||||
: 'name'
|
||||
}}</span>
|
||||
}
|
||||
@case (5) {
|
||||
<span>{{
|
||||
incentive.attributeValue
|
||||
| find
|
||||
: fixedDepositProductsTemplate.chartTemplate.clientClassificationOptions
|
||||
: 'id'
|
||||
: 'name'
|
||||
}}</span>
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{
|
||||
incentive.incentiveType
|
||||
| find
|
||||
: fixedDepositProductsTemplate.chartTemplate.incentiveTypeOptions
|
||||
: 'id'
|
||||
: 'value'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.amount | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns"
|
||||
class="incentives-row"
|
||||
></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<!-- CBS Base Interest Rate Charts -->
|
||||
@if (fdSlabs.length) {
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Interest Rate Charts' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
@if (fixedDepositProductData.charges.length) {
|
||||
<div class="layout-row-wrap responsive-column flex-100">
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="fdSlabs">
|
||||
<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 ?? '—' }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="periodTypeLabel">
|
||||
<th mat-header-cell *matHeaderCellDef>Period</th>
|
||||
<td mat-cell *matCellDef="let slab">{{ slab.periodTypeLabel }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="annualInterestRate">
|
||||
<th mat-header-cell *matHeaderCellDef>Rate (%)</th>
|
||||
<td mat-cell *matCellDef="let slab">
|
||||
<strong>{{ slab.annualInterestRate }}</strong>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="fdSlabColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: fdSlabColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Charges -->
|
||||
@if (fixedDepositProductData.charges?.length) {
|
||||
<div class="layout-row-wrap responsive-column flex-100">
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Charges' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="fixedDepositProductData.charges">
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.name }}
|
||||
</td>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.name }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeCalculationType.value }}
|
||||
</td>
|
||||
<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>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.amount | formatNumber }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="collectedon">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.chargeTimeType.value }}
|
||||
</td>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeTimeType?.value }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
||||
@@ -428,107 +454,16 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Accounting -->
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Accounting' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
|
||||
<mifosx-view-savings-accounting-details
|
||||
class="container"
|
||||
[accountingRule]="fixedDepositProductData.accountingRule"
|
||||
[accountingMappings]="fixedDepositProductData.accountingMappings"
|
||||
>
|
||||
</mifosx-view-savings-accounting-details>
|
||||
|
||||
@if (
|
||||
fixedDepositProductData.advancedAccountingRules &&
|
||||
(fixedDepositProductData.paymentChannelToFundSourceMappings?.length ||
|
||||
fixedDepositProductData.feeToIncomeAccountMappings?.length ||
|
||||
fixedDepositProductData.penaltyToIncomeAccountMappings?.length)
|
||||
) {
|
||||
<div class="flex-100 layout-row-wrap layout-lt-md-column">
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
<hr />
|
||||
@if (fixedDepositProductData.paymentChannelToFundSourceMappings?.length) {
|
||||
<div class="flex-100 layout-row-wrap layout-xs-column">
|
||||
<h4 class="mat-h4 flex-100">
|
||||
{{ 'labels.inputs.Configure Fund Sources for Payment Channels' | translate }}
|
||||
</h4>
|
||||
<table
|
||||
class="mat-elevation-z1 flex-100"
|
||||
mat-table
|
||||
[dataSource]="fixedDepositProductData.paymentChannelToFundSourceMappings"
|
||||
>
|
||||
<ng-container matColumnDef="paymentTypeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payment Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let paymentFundSource">
|
||||
{{ paymentFundSource.paymentType.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.fundSourceAccount.name }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositProductData.feeToIncomeAccountMappings?.length) {
|
||||
<div class="flex-100 layout-row-wrap layout-xs-column">
|
||||
<h4 class="mat-h4 flex-100">{{ 'labels.inputs.Fees to Specific Income Accounts' | translate }}</h4>
|
||||
<table
|
||||
class="mat-elevation-z1 flex-100"
|
||||
mat-table
|
||||
[dataSource]="fixedDepositProductData.feeToIncomeAccountMappings"
|
||||
>
|
||||
<ng-container matColumnDef="chargeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fees' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let feesIncome">
|
||||
{{ feesIncome.charge.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.incomeAccount.name }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@if (fixedDepositProductData.penaltyToIncomeAccountMappings?.length) {
|
||||
<div class="flex-100 layout-row-wrap layout-xs-column">
|
||||
<h4 class="mat-h4 flex-100">
|
||||
{{ 'labels.heading.Penalties to Specific Income Accounts' | translate }}
|
||||
</h4>
|
||||
<table
|
||||
class="mat-elevation-z1 flex-100"
|
||||
mat-table
|
||||
[dataSource]="fixedDepositProductData.penaltyToIncomeAccountMappings"
|
||||
>
|
||||
<ng-container matColumnDef="chargeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let penaltyIncome">
|
||||
{{ penaltyIncome.charge.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.incomeAccount.name }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (!isNoneAccounting()) {
|
||||
<mifosx-view-savings-accounting-details
|
||||
[accountingRule]="fixedDepositProductData.accountingRule"
|
||||
[accountingMappings]="fixedDepositProductData.accountingMappings"
|
||||
></mifosx-view-savings-accounting-details>
|
||||
}
|
||||
</div>
|
||||
</mat-card-content>
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ span {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.mat-card .mat-divider-horizontal.mat-divider-inset {
|
||||
mat-divider {
|
||||
margin: 0 0 0.5em;
|
||||
}
|
||||
|
||||
|
||||
+65
-32
@@ -6,12 +6,10 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, OnInit, inject } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { trigger, state, transition, animate, style } from '@angular/animations';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
@@ -24,6 +22,9 @@ import {
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { OptionData } from 'app/shared/models/option-data.model';
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { ViewSavingsAccountingDetailsComponent } from '../../../../shared/accounting/view-savings-accounting-details/view-savings-accounting-details.component';
|
||||
import { FindPipe } from '../../../../pipes/find.pipe';
|
||||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||||
@@ -35,15 +36,9 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
selector: 'mifosx-fixed-deposit-general-tab',
|
||||
templateUrl: './fixed-deposit-general-tab.component.html',
|
||||
styleUrls: ['./fixed-deposit-general-tab.component.scss'],
|
||||
animations: [
|
||||
trigger('expandChartSlab', [
|
||||
state('collapsed', style({ height: '0px', minHeight: '0' })),
|
||||
state('expanded', style({ height: '*' })),
|
||||
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
|
||||
])
|
||||
],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
RouterLink,
|
||||
FaIconComponent,
|
||||
MatDivider,
|
||||
MatTable,
|
||||
@@ -63,52 +58,90 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
YesnoPipe
|
||||
]
|
||||
})
|
||||
export class FixedDepositGeneralTabComponent {
|
||||
export class FixedDepositGeneralTabComponent implements OnInit {
|
||||
private route = inject(ActivatedRoute);
|
||||
private accounting = inject(Accounting);
|
||||
private productsService = inject(ProductsService);
|
||||
|
||||
/** Fixed Deposit Product data. */
|
||||
fixedDepositProductData: any;
|
||||
fixedDepositProductsTemplate: any;
|
||||
|
||||
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
|
||||
chartSlabsDisplayedColumns: string[] = [
|
||||
'period',
|
||||
'amountRange',
|
||||
'annualInterestRate',
|
||||
fdInterestRates: any[] = [];
|
||||
|
||||
accountingMappings: any = {};
|
||||
accountingRule: OptionData;
|
||||
|
||||
readonly fdSlabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
'actions'
|
||||
'fromPeriod',
|
||||
'toPeriod',
|
||||
'periodTypeLabel',
|
||||
'annualInterestRate'
|
||||
];
|
||||
incentivesDisplayedColumns: string[] = [
|
||||
'entityType',
|
||||
'attributeName',
|
||||
'conditionType',
|
||||
'attributeValue',
|
||||
'incentiveType',
|
||||
'amount'
|
||||
];
|
||||
chargesDisplayedColumns: string[] = [
|
||||
readonly chargesDisplayedColumns = [
|
||||
'name',
|
||||
'type',
|
||||
'amount',
|
||||
'collectedon'
|
||||
];
|
||||
paymentFundSourceDisplayedColumns: string[] = [
|
||||
readonly paymentFundSourceDisplayedColumns = [
|
||||
'paymentTypeId',
|
||||
'fundSourceAccountId'
|
||||
];
|
||||
feesPenaltyIncomeDisplayedColumns: string[] = [
|
||||
readonly feesPenaltyIncomeDisplayedColumns = [
|
||||
'chargeId',
|
||||
'incomeAccountId'
|
||||
];
|
||||
|
||||
/**
|
||||
* Retrieves the fixed deposit product data from `resolve`.
|
||||
* @param {ActivatedRoute} route Activated Route.
|
||||
*/
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { fixedDepositProduct: any; fixedDepositProductsTemplate: any }) => {
|
||||
this.fixedDepositProductData = data.fixedDepositProduct;
|
||||
this.fixedDepositProductsTemplate = data.fixedDepositProductsTemplate;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.productsService.getFdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => {
|
||||
this.fdInterestRates = rates || [];
|
||||
this.buildAccountingMappings();
|
||||
},
|
||||
error: () => {
|
||||
this.fdInterestRates = [];
|
||||
this.buildAccountingMappings();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get activeBaseVersion(): any {
|
||||
return this.fdInterestRates.find((v: any) => v.isActive) ?? this.fdInterestRates[0] ?? null;
|
||||
}
|
||||
|
||||
get fdSlabs(): any[] {
|
||||
return this.activeBaseVersion?.slabs || [];
|
||||
}
|
||||
|
||||
get otherGlMappings(): any {
|
||||
return this.fixedDepositProductData?.otherGlMappings || this.fixedDepositProductData?.otherGlMappingsMap || null;
|
||||
}
|
||||
|
||||
private buildAccountingMappings(): void {
|
||||
const data = this.fixedDepositProductData;
|
||||
if (!data) return;
|
||||
this.accountingRule = data.accountingRule;
|
||||
// accountingMappings come directly from the API response with full account objects
|
||||
this.accountingMappings = data.accountingMappings || {};
|
||||
}
|
||||
|
||||
isNoneAccounting(): boolean {
|
||||
return this.accounting.isNoneAccounting(this.fixedDepositProductData?.accountingRule);
|
||||
}
|
||||
|
||||
formatGlAccountByCode(glCode: string, options: any[]): string {
|
||||
if (!glCode || !Array.isArray(options)) return glCode || '-';
|
||||
const account = options.find((o: any) => o?.glCode === glCode);
|
||||
return account ? `(${account.glCode}) ${account.name}` : glCode;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router';
|
||||
import { ActivatedRoute, RouterLinkActive, RouterLink, RouterOutlet, Router } from '@angular/router';
|
||||
import { MatTabNav, MatTabLink, MatTabNavPanel } from '@angular/material/tabs';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@@ -30,6 +30,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
})
|
||||
export class ViewFixedDepositProductComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
|
||||
fixedDepositDatatables: any = [];
|
||||
|
||||
|
||||
@@ -685,44 +685,49 @@ const routes: Routes = [
|
||||
breadcrumb: 'productId',
|
||||
routeParamBreadcrumb: 'productId'
|
||||
},
|
||||
component: ViewRecurringDepositProductComponent,
|
||||
resolve: {
|
||||
recurringDepositDatatables: SavingProductDatatablesResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'general',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'general',
|
||||
component: RecurringDepositGeneralTabComponent,
|
||||
resolve: {
|
||||
recurringDepositProduct: RecurringDepositProductResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'datatables',
|
||||
component: ViewRecurringDepositProductComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':datatableName',
|
||||
component: RecurringDepositDatatableTabComponent,
|
||||
data: { title: 'Data Table View', routeParamBreadcrumb: 'datatableName' },
|
||||
path: '',
|
||||
redirectTo: 'general',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'general',
|
||||
component: RecurringDepositGeneralTabComponent,
|
||||
resolve: {
|
||||
recurringDepositDatatable: SavingProductDatatableResolver
|
||||
recurringDepositProduct: RecurringDepositProductResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'datatables',
|
||||
children: [
|
||||
{
|
||||
path: ':datatableName',
|
||||
component: RecurringDepositDatatableTabComponent,
|
||||
data: { title: 'Data Table View', routeParamBreadcrumb: 'datatableName' },
|
||||
resolve: {
|
||||
recurringDepositDatatable: SavingProductDatatableResolver
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'interest-rates',
|
||||
component: RdInterestRatesTabComponent,
|
||||
data: { title: 'CBS Base Chart', breadcrumb: 'CBS Base Chart', routeParamBreadcrumb: false },
|
||||
resolve: {
|
||||
rdInterestRates: rdInterestRatesResolver
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'interest-rates',
|
||||
component: RdInterestRatesTabComponent,
|
||||
data: { title: 'CBS Base Chart', breadcrumb: 'CBS Base Chart', routeParamBreadcrumb: false },
|
||||
resolve: {
|
||||
rdInterestRates: rdInterestRatesResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'edit',
|
||||
data: { title: 'Edit Recurring Deposit Product', breadcrumb: 'edit', routeParamBreadcrumb: false },
|
||||
@@ -757,33 +762,38 @@ const routes: Routes = [
|
||||
{
|
||||
path: ':productId',
|
||||
data: { title: 'View Fixed Deposit Product', breadcrumb: 'productId', routeParamBreadcrumb: 'productId' },
|
||||
component: ViewFixedDepositProductComponent,
|
||||
resolve: {
|
||||
fixedDepositDatatables: SavingProductDatatablesResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'general',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'general',
|
||||
component: FixedDepositGeneralTabComponent,
|
||||
resolve: {
|
||||
fixedDepositProduct: FixedDepositProductResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'datatables',
|
||||
component: ViewFixedDepositProductComponent,
|
||||
children: [
|
||||
{
|
||||
path: ':datatableName',
|
||||
component: FixedDepositDatatableTabComponent,
|
||||
data: { title: 'Data Table View', routeParamBreadcrumb: 'datatableName' },
|
||||
path: '',
|
||||
redirectTo: 'general',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'general',
|
||||
component: FixedDepositGeneralTabComponent,
|
||||
resolve: {
|
||||
fixedDepositDatatable: SavingProductDatatableResolver
|
||||
fixedDepositProduct: FixedDepositProductResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'datatables',
|
||||
children: [
|
||||
{
|
||||
path: ':datatableName',
|
||||
component: FixedDepositDatatableTabComponent,
|
||||
data: { title: 'Data Table View', routeParamBreadcrumb: 'datatableName' },
|
||||
resolve: {
|
||||
fixedDepositDatatable: SavingProductDatatableResolver
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -546,6 +546,24 @@ export class ProductsService {
|
||||
return this.http.post('/recurringdepositproducts/baseinterestrates', payload);
|
||||
}
|
||||
|
||||
/** Returns the active FD CBS base interest-rate version(s), optionally filtered by fdType. */
|
||||
getFdBaseInterestRates(fdType?: string): Observable<any> {
|
||||
const url = fdType
|
||||
? `/fixeddepositproducts/baseinterestrates?fdType=${encodeURIComponent(fdType)}`
|
||||
: '/fixeddepositproducts/baseinterestrates';
|
||||
return this.http.get(url);
|
||||
}
|
||||
|
||||
/** Returns the full FD CBS base chart history. */
|
||||
getFdBaseInterestRateHistory(): Observable<any> {
|
||||
return this.http.get('/fixeddepositproducts/baseinterestrates/history');
|
||||
}
|
||||
|
||||
/** Saves a new FD CBS base interest-rate version. */
|
||||
saveFdBaseInterestRates(payload: any): Observable<any> {
|
||||
return this.http.post('/fixeddepositproducts/baseinterestrates', payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Observable<any>} Collateral data.
|
||||
*/
|
||||
|
||||
+1
@@ -149,6 +149,7 @@
|
||||
[recurringDepositProductsTemplate]="recurringDepositProductsTemplate"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[recurringDepositProductFormValid]="recurringDepositProductFormValid"
|
||||
[isDormancyTrackingActive]="recurringDepositProductCurrencyForm.get('dormancyAlert')"
|
||||
></mifosx-recurring-deposit-product-accounting-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
+6
-12
@@ -123,23 +123,23 @@ export class CreateRecurringDepositProductComponent {
|
||||
}
|
||||
|
||||
get recurringDepositProductDetailsForm() {
|
||||
return this.recurringDepositProductDetailsStep.recurringDepositProductDetailsForm;
|
||||
return this.recurringDepositProductDetailsStep?.recurringDepositProductDetailsForm;
|
||||
}
|
||||
|
||||
get recurringDepositProductCurrencyForm() {
|
||||
return this.recurringDepositProductCurrencyStep.recurringDepositProductCurrencyForm;
|
||||
return this.recurringDepositProductCurrencyStep?.recurringDepositProductCurrencyForm;
|
||||
}
|
||||
|
||||
get recurringDepositProductTermsForm() {
|
||||
return this.recurringDepositProductTermsStep.recurringDepositProductTermsForm;
|
||||
return this.recurringDepositProductTermsStep?.recurringDepositProductTermsForm;
|
||||
}
|
||||
|
||||
get recurringDepositProductSettingsForm() {
|
||||
return this.recurringDepositProductSettingsStep.recurringDepositProductSettingsForm;
|
||||
return this.recurringDepositProductSettingsStep?.recurringDepositProductSettingsForm;
|
||||
}
|
||||
|
||||
get recurringDepositProductAccountingForm() {
|
||||
return this.recurringDepositProductAccountingStep.recurringDepositProductAccountingForm;
|
||||
return this.recurringDepositProductAccountingStep?.recurringDepositProductAccountingForm;
|
||||
}
|
||||
|
||||
get totalTabs(): number {
|
||||
@@ -147,13 +147,7 @@ export class CreateRecurringDepositProductComponent {
|
||||
}
|
||||
|
||||
get recurringDepositProductFormValid() {
|
||||
return (
|
||||
this.recurringDepositProductDetailsForm.valid &&
|
||||
this.recurringDepositProductCurrencyForm.valid &&
|
||||
this.recurringDepositProductTermsForm.valid &&
|
||||
this.recurringDepositProductSettingsForm.valid &&
|
||||
this.recurringDepositProductAccountingForm.valid
|
||||
);
|
||||
return !!this.recurringDepositProductDetailsForm?.valid;
|
||||
}
|
||||
|
||||
get activeBaseVersion() {
|
||||
|
||||
+6
-4
@@ -148,12 +148,14 @@
|
||||
<mifosx-recurring-deposit-product-accounting-step
|
||||
[recurringDepositProductsTemplate]="recurringDepositProductsTemplate"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[recurringDepositProductFormValid]="recurringDepositProductFormValidAndNotPrinstine"
|
||||
></mifosx-recurring-deposit-product-accounting-step>
|
||||
[recurringDepositProductFormValid]="recurringDepositProductFormValid"
|
||||
[isDormancyTrackingActive]="recurringDepositProductCurrencyForm.get('dormancyAlert')"
|
||||
>
|
||||
</mifosx-recurring-deposit-product-accounting-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@if (recurringDepositProductFormValidAndNotPrinstine) {
|
||||
@if (recurringDepositProductFormValid) {
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-eye rdp-tab-icon"></i>
|
||||
@@ -189,7 +191,7 @@
|
||||
<i class="fas fa-arrow-right" style="margin-left: 8px"></i>
|
||||
</button>
|
||||
}
|
||||
@if (recurringDepositProductFormValidAndNotPrinstine && selectedTabIndex === totalTabs - 1) {
|
||||
@if (recurringDepositProductFormValid && selectedTabIndex === totalTabs - 1) {
|
||||
<button mat-raised-button color="primary" (click)="submit()">
|
||||
<i class="fas fa-check" style="margin-right: 8px"></i>
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
|
||||
+45
-25
@@ -120,42 +120,31 @@ export class EditRecurringDepositProductComponent {
|
||||
}
|
||||
|
||||
get recurringDepositProductDetailsForm() {
|
||||
return this.recurringDepositProductDetailsStep.recurringDepositProductDetailsForm;
|
||||
return this.recurringDepositProductDetailsStep?.recurringDepositProductDetailsForm;
|
||||
}
|
||||
|
||||
get recurringDepositProductCurrencyForm() {
|
||||
return this.recurringDepositProductCurrencyStep.recurringDepositProductCurrencyForm;
|
||||
return this.recurringDepositProductCurrencyStep?.recurringDepositProductCurrencyForm;
|
||||
}
|
||||
|
||||
get recurringDepositProductTermsForm() {
|
||||
return this.recurringDepositProductTermsStep.recurringDepositProductTermsForm;
|
||||
return this.recurringDepositProductTermsStep?.recurringDepositProductTermsForm;
|
||||
}
|
||||
|
||||
get recurringDepositProductSettingsForm() {
|
||||
return this.recurringDepositProductSettingsStep.recurringDepositProductSettingsForm;
|
||||
return this.recurringDepositProductSettingsStep?.recurringDepositProductSettingsForm;
|
||||
}
|
||||
|
||||
get recurringDepositProductAccountingForm() {
|
||||
return this.recurringDepositProductAccountingStep.recurringDepositProductAccountingForm;
|
||||
return this.recurringDepositProductAccountingStep?.recurringDepositProductAccountingForm;
|
||||
}
|
||||
|
||||
get totalTabs(): number {
|
||||
return this.recurringDepositProductFormValidAndNotPrinstine ? 7 : 6;
|
||||
return this.recurringDepositProductFormValid ? 8 : 7;
|
||||
}
|
||||
|
||||
get recurringDepositProductFormValidAndNotPrinstine() {
|
||||
return (
|
||||
this.recurringDepositProductDetailsForm.valid &&
|
||||
this.recurringDepositProductCurrencyForm.valid &&
|
||||
this.recurringDepositProductTermsForm.valid &&
|
||||
this.recurringDepositProductSettingsForm.valid &&
|
||||
this.recurringDepositProductAccountingForm.valid &&
|
||||
(!this.recurringDepositProductDetailsForm.pristine ||
|
||||
!this.recurringDepositProductCurrencyForm.pristine ||
|
||||
!this.recurringDepositProductTermsForm.pristine ||
|
||||
!this.recurringDepositProductSettingsForm.pristine ||
|
||||
!this.recurringDepositProductAccountingForm.pristine)
|
||||
);
|
||||
get recurringDepositProductFormValid() {
|
||||
return !!this.recurringDepositProductDetailsForm?.valid;
|
||||
}
|
||||
|
||||
get activeBaseVersion() {
|
||||
@@ -173,7 +162,8 @@ export class EditRecurringDepositProductComponent {
|
||||
...this.recurringDepositProductTermsStep.recurringDepositProductTerms,
|
||||
...this.recurringDepositProductSettingsStep.recurringDepositProductSettings,
|
||||
...this.recurringDepositProductChargesStep.recurringDepositProductCharges,
|
||||
...this.recurringDepositProductAccountingStep.recurringDepositProductAccounting
|
||||
...this.recurringDepositProductAccountingStep.recurringDepositProductAccounting,
|
||||
rdSlabs: this.activeBaseSlabs
|
||||
};
|
||||
}
|
||||
|
||||
@@ -185,12 +175,11 @@ export class EditRecurringDepositProductComponent {
|
||||
}
|
||||
|
||||
submit() {
|
||||
// TODO: Update once language and date settings are setup
|
||||
const recurringDepositProduct = {
|
||||
const recurringDepositProduct: any = this.pruneEmptyValues({
|
||||
...this.recurringDepositProduct,
|
||||
charges: this.recurringDepositProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code // locale required for depositAmount
|
||||
};
|
||||
charges: (this.recurringDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code
|
||||
});
|
||||
if (!recurringDepositProduct.description) {
|
||||
recurringDepositProduct.description = '';
|
||||
}
|
||||
@@ -202,4 +191,35 @@ export class EditRecurringDepositProductComponent {
|
||||
this.router.navigate(['../'], { relativeTo: this.route });
|
||||
});
|
||||
}
|
||||
|
||||
private pruneEmptyValues<T>(value: T): T {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => this.pruneEmptyValues(item)).filter((item) => item !== undefined) as T;
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.entries(value as Record<string, any>).reduce(
|
||||
(acc, [
|
||||
key,
|
||||
currentValue
|
||||
]) => {
|
||||
const cleanedValue = this.pruneEmptyValues(currentValue);
|
||||
if (cleanedValue === undefined) {
|
||||
return acc;
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[key]: cleanedValue
|
||||
};
|
||||
},
|
||||
{} as Record<string, any>
|
||||
) as T;
|
||||
}
|
||||
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -36,7 +36,9 @@
|
||||
</div>
|
||||
<div class="rdpa-list">
|
||||
@for (row of section.rows; track row.key) {
|
||||
@if (!row.accrualOnly || isAccrualAccounting()) {
|
||||
@if (
|
||||
(!row.accrualOnly || isAccrualAccounting()) && (!row.dormancyOnly || isDormancyTrackingEnabled())
|
||||
) {
|
||||
<div class="rdpa-row">
|
||||
<div class="rdpa-row__meta">
|
||||
<span
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.rdpa-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
|
||||
+57
-45
@@ -32,6 +32,7 @@ type AccountingRowConfig = {
|
||||
options: GLAccount[];
|
||||
required?: boolean;
|
||||
accrualOnly?: boolean;
|
||||
dormancyOnly?: boolean;
|
||||
};
|
||||
|
||||
type AccountingSectionConfig = {
|
||||
@@ -69,6 +70,7 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
|
||||
@Input() recurringDepositProductsTemplate: any;
|
||||
@Input() accountingRuleData: any;
|
||||
@Input() recurringDepositProductFormValid: boolean;
|
||||
@Input() isDormancyTrackingActive: any;
|
||||
|
||||
recurringDepositProductAccountingForm: UntypedFormGroup;
|
||||
assetAccountOptions: GLAccount[] = [];
|
||||
@@ -101,11 +103,22 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
|
||||
this.recurringDepositProductAccountingForm.patchValue({ accountingRule: accountingRuleId });
|
||||
this.syncAccountingControls(accountingRuleId);
|
||||
this.patchAccountingValues(mappings);
|
||||
this.setDormancyControl();
|
||||
}
|
||||
|
||||
createRecurringDepositProductAccountingForm() {
|
||||
this.recurringDepositProductAccountingForm = this.formBuilder.group({
|
||||
accountingRule: [2],
|
||||
savingsReferenceAccountId: [''],
|
||||
savingsControlAccountId: [''],
|
||||
transfersInSuspenseAccountId: [''],
|
||||
interestOnSavingsAccountId: [''],
|
||||
incomeFromFeeAccountId: [''],
|
||||
incomeFromPenaltyAccountId: [''],
|
||||
feesReceivableAccountId: [''],
|
||||
penaltiesReceivableAccountId: [''],
|
||||
interestPayableAccountId: [''],
|
||||
escheatLiabilityId: [''],
|
||||
assetsOtherGlMappings: this.formBuilder.array([]),
|
||||
liabilitiesOtherGlMappings: this.formBuilder.array([]),
|
||||
expensesOtherGlMappings: this.formBuilder.array([]),
|
||||
@@ -129,7 +142,8 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
|
||||
incomeFromPenaltyAccountId: mappings.incomeFromPenaltyAccount?.id || '',
|
||||
feesReceivableAccountId: mappings.feeReceivableAccount?.id || '',
|
||||
penaltiesReceivableAccountId: mappings.penaltyReceivableAccount?.id || '',
|
||||
interestPayableAccountId: mappings.interestPayableAccount?.id || ''
|
||||
interestPayableAccountId: mappings.interestPayableAccount?.id || '',
|
||||
escheatLiabilityId: mappings.escheatLiabilityAccount?.id || ''
|
||||
});
|
||||
|
||||
if (this.isCashOrAccrualAccounting()) {
|
||||
@@ -215,53 +229,43 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
|
||||
return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null;
|
||||
}
|
||||
|
||||
private setDormancyControl() {
|
||||
this.isDormancyTrackingActive?.valueChanges?.subscribe(() => {
|
||||
this.syncAccountingControls(this.recurringDepositProductAccountingForm.value.accountingRule);
|
||||
});
|
||||
}
|
||||
|
||||
isDormancyTrackingEnabled(): boolean {
|
||||
return this.isDormancyTrackingActive?.value ?? this.isDormancyTrackingActive ?? false;
|
||||
}
|
||||
|
||||
private syncAccountingControls(accountingRule: number) {
|
||||
const addControl = (name: string, required = false) => {
|
||||
if (!this.recurringDepositProductAccountingForm.contains(name)) {
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
name,
|
||||
new UntypedFormControl('', required ? Validators.required : undefined)
|
||||
);
|
||||
} else if (required) {
|
||||
this.recurringDepositProductAccountingForm.get(name)?.setValidators(Validators.required);
|
||||
this.recurringDepositProductAccountingForm.get(name)?.updateValueAndValidity({ emitEvent: false });
|
||||
}
|
||||
};
|
||||
const removeControl = (name: string) => {
|
||||
if (this.recurringDepositProductAccountingForm.contains(name)) {
|
||||
this.recurringDepositProductAccountingForm.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);
|
||||
const setRequired = (name: string, required: boolean) => {
|
||||
const ctrl = this.recurringDepositProductAccountingForm.get(name);
|
||||
if (!ctrl) return;
|
||||
if (required) {
|
||||
ctrl.setValidators(Validators.required);
|
||||
} else {
|
||||
removeControl('feesReceivableAccountId');
|
||||
removeControl('penaltiesReceivableAccountId');
|
||||
removeControl('interestPayableAccountId');
|
||||
ctrl.clearValidators();
|
||||
}
|
||||
} else {
|
||||
[
|
||||
'savingsReferenceAccountId',
|
||||
'savingsControlAccountId',
|
||||
'transfersInSuspenseAccountId',
|
||||
'interestOnSavingsAccountId',
|
||||
'incomeFromFeeAccountId',
|
||||
'incomeFromPenaltyAccountId',
|
||||
'feesReceivableAccountId',
|
||||
'penaltiesReceivableAccountId',
|
||||
'interestPayableAccountId'
|
||||
].forEach(removeControl);
|
||||
ctrl.updateValueAndValidity({ emitEvent: false });
|
||||
};
|
||||
|
||||
const cashOrAccrual = this.accounting.isCashOrAccrualAccountingRuleId(accountingRule);
|
||||
const accrualOnly = this.accounting.isAccrualAccountingRuleId(accountingRule);
|
||||
|
||||
setRequired('savingsReferenceAccountId', cashOrAccrual);
|
||||
setRequired('savingsControlAccountId', cashOrAccrual);
|
||||
setRequired('transfersInSuspenseAccountId', cashOrAccrual);
|
||||
setRequired('interestOnSavingsAccountId', cashOrAccrual);
|
||||
setRequired('incomeFromFeeAccountId', cashOrAccrual);
|
||||
setRequired('incomeFromPenaltyAccountId', cashOrAccrual);
|
||||
setRequired('feesReceivableAccountId', accrualOnly);
|
||||
setRequired('penaltiesReceivableAccountId', accrualOnly);
|
||||
setRequired('interestPayableAccountId', accrualOnly);
|
||||
setRequired('escheatLiabilityId', cashOrAccrual && this.isDormancyTrackingEnabled());
|
||||
|
||||
if (!cashOrAccrual) {
|
||||
this.clearOtherGlMappings();
|
||||
}
|
||||
}
|
||||
@@ -331,6 +335,14 @@ export class RecurringDepositProductAccountingStepComponent implements OnInit {
|
||||
controlName: 'interestPayableAccountId',
|
||||
options: this.liabilityAccountOptions,
|
||||
accrualOnly: true
|
||||
},
|
||||
{
|
||||
key: 'escheatLiabilityId',
|
||||
label: 'Escheat Liability Account',
|
||||
inputLabel: 'Escheat liability account',
|
||||
controlName: 'escheatLiabilityId',
|
||||
options: this.liabilityAccountOptions,
|
||||
dormancyOnly: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'liabilitiesOtherGlMappings',
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.rdpc-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.rdpd-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.rdps-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ $gray-900: #202124;
|
||||
.rdpt-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & {
|
||||
|
||||
+2
-1
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router';
|
||||
import { ActivatedRoute, RouterLinkActive, RouterLink, RouterOutlet, Router } from '@angular/router';
|
||||
import { MatTabNav, MatTabLink, MatTabNavPanel } from '@angular/material/tabs';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@@ -27,6 +27,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
})
|
||||
export class ViewRecurringDepositProductComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router);
|
||||
|
||||
recurringDepositDatatables: any = [];
|
||||
|
||||
|
||||
@@ -5,12 +5,13 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN!
|
||||
/* tslint:disable */
|
||||
export default {
|
||||
'mifos_x': {
|
||||
'version': '260430',
|
||||
'hash': '2d4fb423'
|
||||
'version': '260504',
|
||||
'hash': 'e9d2f4b5'
|
||||
},
|
||||
'allow_switching_backend_instance': true
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user