update saving product code
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(npx *)",
|
||||||
|
"Bash(git add *)",
|
||||||
|
"Bash(git commit *)",
|
||||||
|
"Bash(select-object *)",
|
||||||
|
"Bash(git config *)",
|
||||||
|
"Bash(set *)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"$version": 3
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(npx *)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,9 +87,9 @@ export class Accounting {
|
|||||||
} else if (accountingRuleId === 2) {
|
} else if (accountingRuleId === 2) {
|
||||||
return { code: 'CASH', id: accountingRuleId, value: 'Cash' };
|
return { code: 'CASH', id: accountingRuleId, value: 'Cash' };
|
||||||
} else if (accountingRuleId === 3) {
|
} else if (accountingRuleId === 3) {
|
||||||
return { code: 'ACCRUAL_PERIODIC', id: accountingRuleId, value: 'Accural (periodic)' };
|
return { code: 'ACCRUAL_PERIODIC', id: accountingRuleId, value: 'Accrual (periodic)' };
|
||||||
} else if (accountingRuleId === 4) {
|
} else if (accountingRuleId === 4) {
|
||||||
return { code: 'ACCRUAL_UPFRONT', id: accountingRuleId, value: 'Accural (upfront)' };
|
return { code: 'ACCRUAL_UPFRONT', id: accountingRuleId, value: 'Accrual (upfront)' };
|
||||||
}
|
}
|
||||||
return { code: 'INVALID', id: accountingRuleId, value: 'Invalid' };
|
return { code: 'INVALID', id: accountingRuleId, value: 'Invalid' };
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-5
@@ -12,14 +12,21 @@
|
|||||||
<h2 class="sp-header__title">{{ 'labels.heading.Create Savings Product' | translate }}</h2>
|
<h2 class="sp-header__title">{{ 'labels.heading.Create Savings Product' | translate }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-tab-group class="sp-tabs" animationDuration="200ms" [preserveContent]="true" [(selectedIndex)]="selectedTabIndex">
|
<mat-tab-group
|
||||||
|
class="sp-tabs"
|
||||||
|
animationDuration="200ms"
|
||||||
|
[preserveContent]="true"
|
||||||
|
[(selectedIndex)]="selectedTabIndex"
|
||||||
|
>
|
||||||
<mat-tab>
|
<mat-tab>
|
||||||
<ng-template mat-tab-label>
|
<ng-template mat-tab-label>
|
||||||
<i class="fas fa-info-circle sp-tab-icon"></i>
|
<i class="fas fa-info-circle sp-tab-icon"></i>
|
||||||
{{ 'labels.inputs.DETAILS' | translate }}
|
{{ 'labels.inputs.DETAILS' | translate }}
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div class="sp-tab-content">
|
<div class="sp-tab-content">
|
||||||
<mifosx-saving-product-details-step></mifosx-saving-product-details-step>
|
<mifosx-saving-product-details-step
|
||||||
|
[savingProductsTemplate]="savingProductsTemplate"
|
||||||
|
></mifosx-saving-product-details-step>
|
||||||
</div>
|
</div>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
|
||||||
@@ -72,6 +79,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
|
||||||
|
<mat-tab>
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<i class="fas fa-mobile-alt sp-tab-icon"></i>
|
||||||
|
Digital
|
||||||
|
</ng-template>
|
||||||
|
<div class="sp-tab-content">
|
||||||
|
<mifosx-saving-product-limits-step
|
||||||
|
[savingProductsTemplate]="savingProductsTemplate"
|
||||||
|
></mifosx-saving-product-limits-step>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
|
|
||||||
<mat-tab>
|
<mat-tab>
|
||||||
<ng-template mat-tab-label>
|
<ng-template mat-tab-label>
|
||||||
<i class="fas fa-book sp-tab-icon"></i>
|
<i class="fas fa-book sp-tab-icon"></i>
|
||||||
@@ -114,19 +133,19 @@
|
|||||||
<div class="sp-action-bar__right">
|
<div class="sp-action-bar__right">
|
||||||
@if (selectedTabIndex > 0) {
|
@if (selectedTabIndex > 0) {
|
||||||
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
||||||
<i class="fas fa-arrow-left" style="margin-right: 8px;"></i>
|
<i class="fas fa-arrow-left" style="margin-right: 8px"></i>
|
||||||
{{ 'labels.buttons.Previous' | translate }}
|
{{ 'labels.buttons.Previous' | translate }}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@if (selectedTabIndex < totalTabs - 1) {
|
@if (selectedTabIndex < totalTabs - 1) {
|
||||||
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
{{ 'labels.buttons.Next' | translate }}
|
||||||
<i class="fas fa-arrow-right" style="margin-left: 8px;"></i>
|
<i class="fas fa-arrow-right" style="margin-left: 8px"></i>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@if (savingProductFormValid && selectedTabIndex === totalTabs - 1) {
|
@if (savingProductFormValid && selectedTabIndex === totalTabs - 1) {
|
||||||
<button mat-raised-button color="primary" (click)="submit()">
|
<button mat-raised-button color="primary" (click)="submit()">
|
||||||
<i class="fas fa-check" style="margin-right: 8px;"></i>
|
<i class="fas fa-check" style="margin-right: 8px"></i>
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -35,11 +35,11 @@ $primary: #1353a0;
|
|||||||
.sp-tabs {
|
.sp-tabs {
|
||||||
background: var(--md-sys-color-surface, #fff);
|
background: var(--md-sys-color-surface, #fff);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
.mat-mdc-tab-header {
|
.mat-mdc-tab-header {
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-tab {
|
.mat-mdc-tab {
|
||||||
@@ -77,10 +77,10 @@ $primary: #1353a0;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||||
|
|
||||||
&__cancel {
|
&__cancel {
|
||||||
color: rgba(0, 0, 0, 0.6);
|
color: rgb(0 0 0 / 60%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__right {
|
&__right {
|
||||||
|
|||||||
+35
-5
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright since 2025 Mifos Initiative
|
* Copyright since 2025 Mifos Initiative
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
@@ -16,6 +16,7 @@ import { SavingProductCurrencyStepComponent } from '../saving-product-stepper/sa
|
|||||||
import { SavingProductTermsStepComponent } from '../saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component';
|
import { SavingProductTermsStepComponent } from '../saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component';
|
||||||
import { SavingProductSettingsStepComponent } from '../saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component';
|
import { SavingProductSettingsStepComponent } from '../saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component';
|
||||||
import { SavingProductChargesStepComponent } from '../saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component';
|
import { SavingProductChargesStepComponent } from '../saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component';
|
||||||
|
import { SavingProductLimitsStepComponent } from '../saving-product-stepper/saving-product-limits-step/saving-product-limits-step.component';
|
||||||
import { SavingProductAccountingStepComponent } from '../saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component';
|
import { SavingProductAccountingStepComponent } from '../saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component';
|
||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
@@ -38,6 +39,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
SavingProductTermsStepComponent,
|
SavingProductTermsStepComponent,
|
||||||
SavingProductSettingsStepComponent,
|
SavingProductSettingsStepComponent,
|
||||||
SavingProductChargesStepComponent,
|
SavingProductChargesStepComponent,
|
||||||
|
SavingProductLimitsStepComponent,
|
||||||
SavingProductAccountingStepComponent,
|
SavingProductAccountingStepComponent,
|
||||||
SavingProductPreviewStepComponent
|
SavingProductPreviewStepComponent
|
||||||
]
|
]
|
||||||
@@ -58,6 +60,8 @@ export class CreateSavingProductComponent {
|
|||||||
savingProductSettingsStep: SavingProductSettingsStepComponent;
|
savingProductSettingsStep: SavingProductSettingsStepComponent;
|
||||||
@ViewChild(SavingProductChargesStepComponent, { static: true })
|
@ViewChild(SavingProductChargesStepComponent, { static: true })
|
||||||
savingProductChargesStep: SavingProductChargesStepComponent;
|
savingProductChargesStep: SavingProductChargesStepComponent;
|
||||||
|
@ViewChild(SavingProductLimitsStepComponent, { static: true })
|
||||||
|
savingProductLimitsStep: SavingProductLimitsStepComponent;
|
||||||
@ViewChild(SavingProductAccountingStepComponent, { static: true })
|
@ViewChild(SavingProductAccountingStepComponent, { static: true })
|
||||||
savingProductAccountingStep: SavingProductAccountingStepComponent;
|
savingProductAccountingStep: SavingProductAccountingStepComponent;
|
||||||
|
|
||||||
@@ -100,7 +104,7 @@ export class CreateSavingProductComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get totalTabs(): number {
|
get totalTabs(): number {
|
||||||
return this.savingProductFormValid ? 7 : 6;
|
return this.savingProductFormValid ? 8 : 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
get savingProductFormValid() {
|
get savingProductFormValid() {
|
||||||
@@ -120,18 +124,44 @@ export class CreateSavingProductComponent {
|
|||||||
...this.savingProductTermsStep.savingProductTerms,
|
...this.savingProductTermsStep.savingProductTerms,
|
||||||
...this.savingProductSettingsStep.savingProductSettings,
|
...this.savingProductSettingsStep.savingProductSettings,
|
||||||
...this.savingProductChargesStep.savingProductCharges,
|
...this.savingProductChargesStep.savingProductCharges,
|
||||||
|
...this.savingProductLimitsStep.savingProductLimits,
|
||||||
...this.savingProductAccountingStep.savingProductAccounting
|
...this.savingProductAccountingStep.savingProductAccounting
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
private prepareSavingProductPayload() {
|
||||||
// TODO: Update once language and date settings are setup
|
|
||||||
const savingProduct = {
|
const savingProduct = {
|
||||||
...this.savingProduct,
|
...this.savingProduct,
|
||||||
charges: this.savingProduct.charges.map((charge: any) => ({ id: charge.id })),
|
charges: this.savingProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||||
locale: this.settingsService.language.code // locale required for nominalAnnualInterestRate
|
locale: this.settingsService.language.code,
|
||||||
|
dateFormat: 'dd MMMM yyyy'
|
||||||
};
|
};
|
||||||
delete savingProduct.advancedAccountingRules;
|
delete savingProduct.advancedAccountingRules;
|
||||||
|
|
||||||
|
// Convert lockinPeriodFrequencyType from string to integer
|
||||||
|
// Backend expects: 0=DAYS, 1=WEEKS, 2=MONTHS, 3=YEARS
|
||||||
|
if (savingProduct.lockinPeriodFrequencyType) {
|
||||||
|
const frequencyTypeMapping: { [key: string]: number } = {
|
||||||
|
DAYS: 0,
|
||||||
|
WEEKS: 1,
|
||||||
|
MONTHS: 2,
|
||||||
|
YEARS: 3
|
||||||
|
};
|
||||||
|
savingProduct.lockinPeriodFrequencyType = frequencyTypeMapping[savingProduct.lockinPeriodFrequencyType];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(savingProduct).forEach((key) => {
|
||||||
|
if (savingProduct[key] === '' || savingProduct[key] === null || savingProduct[key] === undefined) {
|
||||||
|
delete savingProduct[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return savingProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
const savingProduct = this.prepareSavingProductPayload();
|
||||||
|
console.log('Saving product submit payload:', savingProduct);
|
||||||
this.productsService.createSavingProduct(savingProduct).subscribe((response: any) => {
|
this.productsService.createSavingProduct(savingProduct).subscribe((response: any) => {
|
||||||
this.router.navigate(
|
this.router.navigate(
|
||||||
[
|
[
|
||||||
|
|||||||
+21
-4
@@ -12,7 +12,12 @@
|
|||||||
<h2 class="sp-header__title">{{ 'labels.heading.Edit Savings Product' | translate }}</h2>
|
<h2 class="sp-header__title">{{ 'labels.heading.Edit Savings Product' | translate }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-tab-group class="sp-tabs" animationDuration="200ms" [preserveContent]="true" [(selectedIndex)]="selectedTabIndex">
|
<mat-tab-group
|
||||||
|
class="sp-tabs"
|
||||||
|
animationDuration="200ms"
|
||||||
|
[preserveContent]="true"
|
||||||
|
[(selectedIndex)]="selectedTabIndex"
|
||||||
|
>
|
||||||
<mat-tab>
|
<mat-tab>
|
||||||
<ng-template mat-tab-label>
|
<ng-template mat-tab-label>
|
||||||
<i class="fas fa-info-circle sp-tab-icon"></i>
|
<i class="fas fa-info-circle sp-tab-icon"></i>
|
||||||
@@ -74,6 +79,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
|
||||||
|
<mat-tab>
|
||||||
|
<ng-template mat-tab-label>
|
||||||
|
<i class="fas fa-mobile-alt sp-tab-icon"></i>
|
||||||
|
Digital
|
||||||
|
</ng-template>
|
||||||
|
<div class="sp-tab-content">
|
||||||
|
<mifosx-saving-product-limits-step
|
||||||
|
[savingProductsTemplate]="savingProductAndTemplate"
|
||||||
|
></mifosx-saving-product-limits-step>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
|
|
||||||
<mat-tab>
|
<mat-tab>
|
||||||
<ng-template mat-tab-label>
|
<ng-template mat-tab-label>
|
||||||
<i class="fas fa-book sp-tab-icon"></i>
|
<i class="fas fa-book sp-tab-icon"></i>
|
||||||
@@ -116,19 +133,19 @@
|
|||||||
<div class="sp-action-bar__right">
|
<div class="sp-action-bar__right">
|
||||||
@if (selectedTabIndex > 0) {
|
@if (selectedTabIndex > 0) {
|
||||||
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
||||||
<i class="fas fa-arrow-left" style="margin-right: 8px;"></i>
|
<i class="fas fa-arrow-left" style="margin-right: 8px"></i>
|
||||||
{{ 'labels.buttons.Previous' | translate }}
|
{{ 'labels.buttons.Previous' | translate }}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@if (selectedTabIndex < totalTabs - 1) {
|
@if (selectedTabIndex < totalTabs - 1) {
|
||||||
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
||||||
{{ 'labels.buttons.Next' | translate }}
|
{{ 'labels.buttons.Next' | translate }}
|
||||||
<i class="fas fa-arrow-right" style="margin-left: 8px;"></i>
|
<i class="fas fa-arrow-right" style="margin-left: 8px"></i>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
@if (savingProductFormValidAndNotPristine && selectedTabIndex === totalTabs - 1) {
|
@if (savingProductFormValidAndNotPristine && selectedTabIndex === totalTabs - 1) {
|
||||||
<button mat-raised-button color="primary" (click)="submit()">
|
<button mat-raised-button color="primary" (click)="submit()">
|
||||||
<i class="fas fa-check" style="margin-right: 8px;"></i>
|
<i class="fas fa-check" style="margin-right: 8px"></i>
|
||||||
{{ 'labels.buttons.Submit' | translate }}
|
{{ 'labels.buttons.Submit' | translate }}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -35,11 +35,11 @@ $primary: #1353a0;
|
|||||||
.sp-tabs {
|
.sp-tabs {
|
||||||
background: var(--md-sys-color-surface, #fff);
|
background: var(--md-sys-color-surface, #fff);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||||
|
|
||||||
::ng-deep {
|
::ng-deep {
|
||||||
.mat-mdc-tab-header {
|
.mat-mdc-tab-header {
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-tab {
|
.mat-mdc-tab {
|
||||||
@@ -77,10 +77,10 @@ $primary: #1353a0;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
padding: 16px 0;
|
padding: 16px 0;
|
||||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||||
|
|
||||||
&__cancel {
|
&__cancel {
|
||||||
color: rgba(0, 0, 0, 0.6);
|
color: rgb(0 0 0 / 60%);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__right {
|
&__right {
|
||||||
|
|||||||
+36
-5
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright since 2025 Mifos Initiative
|
* Copyright since 2025 Mifos Initiative
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
@@ -16,6 +16,7 @@ import { SavingProductCurrencyStepComponent } from '../saving-product-stepper/sa
|
|||||||
import { SavingProductTermsStepComponent } from '../saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component';
|
import { SavingProductTermsStepComponent } from '../saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component';
|
||||||
import { SavingProductSettingsStepComponent } from '../saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component';
|
import { SavingProductSettingsStepComponent } from '../saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component';
|
||||||
import { SavingProductChargesStepComponent } from '../saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component';
|
import { SavingProductChargesStepComponent } from '../saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component';
|
||||||
|
import { SavingProductLimitsStepComponent } from '../saving-product-stepper/saving-product-limits-step/saving-product-limits-step.component';
|
||||||
import { SavingProductAccountingStepComponent } from '../saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component';
|
import { SavingProductAccountingStepComponent } from '../saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component';
|
||||||
|
|
||||||
/** Custom Services */
|
/** Custom Services */
|
||||||
@@ -38,6 +39,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
SavingProductTermsStepComponent,
|
SavingProductTermsStepComponent,
|
||||||
SavingProductSettingsStepComponent,
|
SavingProductSettingsStepComponent,
|
||||||
SavingProductChargesStepComponent,
|
SavingProductChargesStepComponent,
|
||||||
|
SavingProductLimitsStepComponent,
|
||||||
SavingProductAccountingStepComponent,
|
SavingProductAccountingStepComponent,
|
||||||
SavingProductPreviewStepComponent
|
SavingProductPreviewStepComponent
|
||||||
]
|
]
|
||||||
@@ -58,6 +60,8 @@ export class EditSavingProductComponent {
|
|||||||
savingProductSettingsStep: SavingProductSettingsStepComponent;
|
savingProductSettingsStep: SavingProductSettingsStepComponent;
|
||||||
@ViewChild(SavingProductChargesStepComponent, { static: true })
|
@ViewChild(SavingProductChargesStepComponent, { static: true })
|
||||||
savingProductChargesStep: SavingProductChargesStepComponent;
|
savingProductChargesStep: SavingProductChargesStepComponent;
|
||||||
|
@ViewChild(SavingProductLimitsStepComponent, { static: true })
|
||||||
|
savingProductLimitsStep: SavingProductLimitsStepComponent;
|
||||||
@ViewChild(SavingProductAccountingStepComponent, { static: true })
|
@ViewChild(SavingProductAccountingStepComponent, { static: true })
|
||||||
savingProductAccountingStep: SavingProductAccountingStepComponent;
|
savingProductAccountingStep: SavingProductAccountingStepComponent;
|
||||||
|
|
||||||
@@ -100,7 +104,7 @@ export class EditSavingProductComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get totalTabs(): number {
|
get totalTabs(): number {
|
||||||
return this.savingProductFormValidAndNotPristine ? 7 : 6;
|
return this.savingProductFormValidAndNotPristine ? 8 : 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
get savingProductFormValidAndNotPristine() {
|
get savingProductFormValidAndNotPristine() {
|
||||||
@@ -115,6 +119,7 @@ export class EditSavingProductComponent {
|
|||||||
!this.savingProductTermsForm.pristine ||
|
!this.savingProductTermsForm.pristine ||
|
||||||
!this.savingProductSettingsForm.pristine ||
|
!this.savingProductSettingsForm.pristine ||
|
||||||
!this.savingProductChargesStep.pristine ||
|
!this.savingProductChargesStep.pristine ||
|
||||||
|
!this.savingProductLimitsStep.pristine ||
|
||||||
!this.savingProductAccountingForm.pristine)
|
!this.savingProductAccountingForm.pristine)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -126,18 +131,44 @@ export class EditSavingProductComponent {
|
|||||||
...this.savingProductTermsStep.savingProductTerms,
|
...this.savingProductTermsStep.savingProductTerms,
|
||||||
...this.savingProductSettingsStep.savingProductSettings,
|
...this.savingProductSettingsStep.savingProductSettings,
|
||||||
...this.savingProductChargesStep.savingProductCharges,
|
...this.savingProductChargesStep.savingProductCharges,
|
||||||
|
...this.savingProductLimitsStep.savingProductLimits,
|
||||||
...this.savingProductAccountingStep.savingProductAccounting
|
...this.savingProductAccountingStep.savingProductAccounting
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
private prepareSavingProductPayload() {
|
||||||
// TODO: Update once language and date settings are setup
|
|
||||||
const savingProduct = {
|
const savingProduct = {
|
||||||
...this.savingProduct,
|
...this.savingProduct,
|
||||||
charges: this.savingProduct.charges.map((charge: any) => ({ id: charge.id })),
|
charges: this.savingProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||||
locale: this.settingsService.language.code // locale required for nominalAnnualInterestRate
|
locale: this.settingsService.language.code,
|
||||||
|
dateFormat: 'dd MMMM yyyy'
|
||||||
};
|
};
|
||||||
delete savingProduct.advancedAccountingRules;
|
delete savingProduct.advancedAccountingRules;
|
||||||
|
|
||||||
|
// Convert lockinPeriodFrequencyType from string to integer
|
||||||
|
// Backend expects: 0=DAYS, 1=WEEKS, 2=MONTHS, 3=YEARS
|
||||||
|
if (savingProduct.lockinPeriodFrequencyType) {
|
||||||
|
const frequencyTypeMapping: { [key: string]: number } = {
|
||||||
|
DAYS: 0,
|
||||||
|
WEEKS: 1,
|
||||||
|
MONTHS: 2,
|
||||||
|
YEARS: 3
|
||||||
|
};
|
||||||
|
savingProduct.lockinPeriodFrequencyType = frequencyTypeMapping[savingProduct.lockinPeriodFrequencyType];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(savingProduct).forEach((key) => {
|
||||||
|
if (savingProduct[key] === '' || savingProduct[key] === null || savingProduct[key] === undefined) {
|
||||||
|
delete savingProduct[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return savingProduct;
|
||||||
|
}
|
||||||
|
|
||||||
|
submit() {
|
||||||
|
const savingProduct = this.prepareSavingProductPayload();
|
||||||
|
console.log('Saving product update payload:', savingProduct);
|
||||||
this.productsService
|
this.productsService
|
||||||
.updateSavingProduct(this.savingProductAndTemplate.id, savingProduct)
|
.updateSavingProduct(this.savingProductAndTemplate.id, savingProduct)
|
||||||
.subscribe((response: any) => {
|
.subscribe((response: any) => {
|
||||||
|
|||||||
+111
-341
@@ -1,4 +1,4 @@
|
|||||||
<!--
|
<!--
|
||||||
Copyright since 2025 Mifos Initiative
|
Copyright since 2025 Mifos Initiative
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
@@ -7,356 +7,126 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<form [formGroup]="savingProductAccountingForm">
|
<form [formGroup]="savingProductAccountingForm">
|
||||||
<!-- Accounting Rule -->
|
|
||||||
<div class="spa-section">
|
<div class="spa-section">
|
||||||
<div class="spa-section__header">
|
<div class="spa-section__header">
|
||||||
<i class="fas fa-book"></i>
|
<span class="spa-section__icon">
|
||||||
<h3>{{ 'labels.inputs.Accounting Rule' | translate }}</h3>
|
<i class="fas fa-book"></i>
|
||||||
</div>
|
</span>
|
||||||
<div class="spa-body">
|
<h3>Accounting</h3>
|
||||||
<mat-radio-group class="spa-radio-group" formControlName="accountingRule">
|
|
||||||
@for (accountingRule of accountingRuleData; track accountingRule; let i = $index) {
|
|
||||||
<mat-radio-button [value]="i + 1">
|
|
||||||
{{ 'labels.accounting.' + accountingRule | translate }}
|
|
||||||
</mat-radio-button>
|
|
||||||
}
|
|
||||||
</mat-radio-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (isCashOrAccrualAccounting()) {
|
|
||||||
<!-- Assets -->
|
|
||||||
<div class="spa-section">
|
|
||||||
<div class="spa-section__header">
|
|
||||||
<i class="fas fa-university"></i>
|
|
||||||
<h3>{{ 'labels.heading.Assets' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="spa-grid">
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.savingsReferenceAccountId"
|
|
||||||
[glAccountList]="assetAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Saving reference'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.overdraftPortfolioControlId"
|
|
||||||
[glAccountList]="assetAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Overdraft portfolio'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
|
|
||||||
@if (isAccrualAccounting()) {
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.feesReceivableAccountId"
|
|
||||||
[glAccountList]="assetAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Fees Receivable'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.penaltiesReceivableAccountId"
|
|
||||||
[glAccountList]="assetAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Penalties Receivable'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (isAccrualAccounting() && allowOverdraft.value) {
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.interestReceivableAccountId"
|
|
||||||
[glAccountList]="assetAccountData"
|
|
||||||
[required]="false"
|
|
||||||
[inputLabel]="'Interest Receivable'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Liabilities -->
|
<div class="spa-section__body">
|
||||||
<div class="spa-section">
|
<div class="spa-group">
|
||||||
<div class="spa-section__header">
|
<div class="spa-group__header">
|
||||||
<i class="fas fa-file-invoice-dollar"></i>
|
<h4>Accounting Rule</h4>
|
||||||
<h3>{{ 'labels.heading.Liabilities' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="spa-grid">
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.savingsControlAccountId"
|
|
||||||
[glAccountList]="liabilityAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Saving control'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.transfersInSuspenseAccountId"
|
|
||||||
[glAccountList]="liabilityAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Savings transfers in suspense'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
|
|
||||||
@if (isAccrualAccounting()) {
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.interestPayableAccountId"
|
|
||||||
[glAccountList]="liabilityAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Interest Payable'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (isDormancyTrackingActive.value) {
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.escheatLiabilityId"
|
|
||||||
[glAccountList]="liabilityAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Escheat liability'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Expenses -->
|
|
||||||
<div class="spa-section">
|
|
||||||
<div class="spa-section__header">
|
|
||||||
<i class="fas fa-money-bill-wave"></i>
|
|
||||||
<h3>{{ 'labels.heading.Expenses' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="spa-grid">
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.interestOnSavingsAccountId"
|
|
||||||
[glAccountList]="expenseAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Interest on savings'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.writeOffAccountId"
|
|
||||||
[glAccountList]="expenseAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Write-off'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Income -->
|
|
||||||
<div class="spa-section">
|
|
||||||
<div class="spa-section__header">
|
|
||||||
<i class="fas fa-chart-line"></i>
|
|
||||||
<h3>{{ 'labels.heading.Income' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="spa-grid">
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.incomeFromFeeAccountId"
|
|
||||||
[glAccountList]="incomeAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Income from fees'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.incomeFromPenaltyAccountId"
|
|
||||||
[glAccountList]="incomeAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Income from penalties'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
|
|
||||||
<mifosx-gl-account-selector
|
|
||||||
class="spa-field"
|
|
||||||
[inputFormControl]="savingProductAccountingForm.controls.incomeFromInterestId"
|
|
||||||
[glAccountList]="incomeAccountData"
|
|
||||||
[required]="true"
|
|
||||||
[inputLabel]="'Overdraft Interest Income'"
|
|
||||||
></mifosx-gl-account-selector>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Advanced Accounting Rules -->
|
|
||||||
<div class="spa-section">
|
|
||||||
<div class="spa-section__header">
|
|
||||||
<i class="fas fa-cogs"></i>
|
|
||||||
<h3>{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="spa-body">
|
|
||||||
<div class="spa-checkbox-row">
|
|
||||||
<mat-checkbox formControlName="advancedAccountingRules">
|
|
||||||
{{ 'labels.heading.Advanced Accounting Rules' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
</div>
|
||||||
|
<mat-radio-group class="spa-radio-group" formControlName="accountingRule">
|
||||||
|
@for (accountingRule of accountingRuleData; track accountingRule; let i = $index) {
|
||||||
|
<mat-radio-button [value]="i + 1">
|
||||||
|
{{ 'labels.accounting.' + accountingRule | translate }}
|
||||||
|
</mat-radio-button>
|
||||||
|
}
|
||||||
|
</mat-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (savingProductAccountingForm.value.advancedAccountingRules) {
|
@if (!isCashOrAccrualAccounting()) {
|
||||||
<!-- Payment Fund Sources -->
|
<div class="spa-note">
|
||||||
<div class="spa-subsection">
|
Select a cash or accrual accounting rule to configure GL mappings. The default "None" rule does not require
|
||||||
<div class="spa-subsection__header">
|
any accounting mappings.
|
||||||
<h4>{{ 'labels.heading.Configure Fund Sources for Payment Channels' | translate }}</h4>
|
</div>
|
||||||
<button
|
}
|
||||||
type="button"
|
|
||||||
mat-flat-button
|
@if (isCashOrAccrualAccounting()) {
|
||||||
color="primary"
|
@for (section of accountingSectionConfigs; track section.key) {
|
||||||
(click)="add('PaymentFundSource', paymentChannelToFundSourceMappings)"
|
<div class="spa-group">
|
||||||
class="spa-add-btn"
|
<div class="spa-group__header">
|
||||||
>
|
<h4>{{ section.title }}</h4>
|
||||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
<button type="button" class="spa-add-btn" (click)="addOtherGlMapping(section.otherMappingsControlName)">
|
||||||
{{ 'labels.buttons.Add' | translate }}
|
+ Add Other
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="spa-list">
|
||||||
@if (paymentChannelToFundSourceMappings.value.length !== 0) {
|
@for (row of section.rows; track row.key) {
|
||||||
<div class="spa-table-wrap">
|
@if (
|
||||||
<table mat-table [dataSource]="paymentChannelToFundSourceMappings.value" class="spa-table">
|
(!row.accrualOnly || isAccrualAccounting()) && (!row.dormancyOnly || isDormancyTrackingEnabled())
|
||||||
<ng-container matColumnDef="paymentTypeId">
|
) {
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payment Type' | translate }}</th>
|
<div class="spa-row">
|
||||||
<td mat-cell *matCellDef="let paymentFundSource">
|
<div class="spa-row__meta">
|
||||||
{{ paymentFundSource.paymentTypeId | find: paymentTypeData : 'id' : 'name' }}
|
<span
|
||||||
</td>
|
class="spa-pill"
|
||||||
</ng-container>
|
[class.spa-pill--green]="section.pillClass === 'green'"
|
||||||
<ng-container matColumnDef="fundSourceAccountId">
|
[class.spa-pill--amber]="section.pillClass === 'amber'"
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fund Source' | translate }}</th>
|
[class.spa-pill--blue]="section.pillClass === 'blue'"
|
||||||
<td mat-cell *matCellDef="let paymentFundSource">
|
|
||||||
{{ paymentFundSource.fundSourceAccountId | find: combinedAccountData : 'id' : 'name' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="actions">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let paymentFundSource; let i = index">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
(click)="edit('PaymentFundSource', paymentChannelToFundSourceMappings, i)"
|
|
||||||
>
|
>
|
||||||
<fa-icon icon="edit"></fa-icon>
|
{{ section.pillLabel }}
|
||||||
</button>
|
</span>
|
||||||
<button mat-icon-button color="warn" (click)="delete(paymentChannelToFundSourceMappings, i)">
|
<span class="spa-row__label">{{ row.label }}</span>
|
||||||
<fa-icon icon="trash"></fa-icon>
|
</div>
|
||||||
</button>
|
<mifosx-gl-account-selector
|
||||||
</td>
|
class="spa-row__input"
|
||||||
</ng-container>
|
[inputFormControl]="savingProductAccountingForm.get(row.controlName)"
|
||||||
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
|
[glAccountList]="row.options"
|
||||||
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
|
[required]="row.required"
|
||||||
</table>
|
[inputLabel]="row.inputLabel"
|
||||||
</div>
|
></mifosx-gl-account-selector>
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spa-list" [formArrayName]="section.otherMappingsControlName">
|
||||||
|
@for (
|
||||||
|
otherRow of getOtherGlMappingsArray(section.otherMappingsControlName).controls;
|
||||||
|
track $index;
|
||||||
|
let i = $index
|
||||||
|
) {
|
||||||
|
<div class="spa-other-row-wrap">
|
||||||
|
<div class="spa-row spa-row--other" [formGroupName]="i">
|
||||||
|
<div class="spa-row__meta spa-row__meta--other">
|
||||||
|
<span
|
||||||
|
class="spa-pill"
|
||||||
|
[class.spa-pill--green]="section.pillClass === 'green'"
|
||||||
|
[class.spa-pill--amber]="section.pillClass === 'amber'"
|
||||||
|
[class.spa-pill--blue]="section.pillClass === 'blue'"
|
||||||
|
>
|
||||||
|
{{ section.pillLabel }}
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
class="spa-row__name spa-row__name--other-account"
|
||||||
|
formControlName="accountName"
|
||||||
|
placeholder="Enter account name..."
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spa-row__actions spa-row__actions--other">
|
||||||
|
<mat-form-field class="spa-row__other-field" appearance="outline" subscriptSizing="dynamic">
|
||||||
|
<mat-label>GL Code</mat-label>
|
||||||
|
<mat-select
|
||||||
|
[formControl]="getOtherGlMappingControl(section.otherMappingsControlName, i, 'glAccountId')"
|
||||||
|
>
|
||||||
|
@for (glAccount of section.otherAccountOptions; track glAccount.id) {
|
||||||
|
<mat-option [value]="glAccount.id">
|
||||||
|
{{ '(' + glAccount.glCode + ') ' + glAccount.name }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="spa-remove-btn spa-remove-btn--side"
|
||||||
|
(click)="removeOtherGlMapping(section.otherMappingsControlName, i)"
|
||||||
|
>
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Fees to Income -->
|
|
||||||
@if (existCharges()) {
|
|
||||||
<div class="spa-subsection">
|
|
||||||
<div class="spa-subsection__header">
|
|
||||||
<h4>{{ 'labels.heading.Map Fees to Specific Income Accounts' | translate }}</h4>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
mat-flat-button
|
|
||||||
color="primary"
|
|
||||||
(click)="add('FeesIncome', feeToIncomeAccountMappings)"
|
|
||||||
class="spa-add-btn"
|
|
||||||
>
|
|
||||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.Add' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (feeToIncomeAccountMappings.value.length !== 0) {
|
|
||||||
<div class="spa-table-wrap">
|
|
||||||
<table mat-table [dataSource]="feeToIncomeAccountMappings.value" class="spa-table">
|
|
||||||
<ng-container matColumnDef="chargeId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fees' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let feesIncome">
|
|
||||||
{{ feesIncome.chargeId | find: chargeData : 'id' : 'name' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="incomeAccountId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let feesIncome">
|
|
||||||
{{ feesIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="actions">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let feesIncome; let i = index">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
(click)="edit('FeesIncome', feeToIncomeAccountMappings, i)"
|
|
||||||
>
|
|
||||||
<fa-icon icon="edit"></fa-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-icon-button color="warn" (click)="delete(feeToIncomeAccountMappings, i)">
|
|
||||||
<fa-icon icon="trash"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Penalties to Income -->
|
|
||||||
<div class="spa-subsection">
|
|
||||||
<div class="spa-subsection__header">
|
|
||||||
<h4>{{ 'labels.heading.Map Penalties to Specific Income Accounts' | translate }}</h4>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
mat-flat-button
|
|
||||||
color="primary"
|
|
||||||
(click)="add('PenaltyIncome', penaltyToIncomeAccountMappings)"
|
|
||||||
class="spa-add-btn"
|
|
||||||
>
|
|
||||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
|
||||||
{{ 'labels.buttons.Add' | translate }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (penaltyToIncomeAccountMappings.value.length !== 0) {
|
|
||||||
<div class="spa-table-wrap">
|
|
||||||
<table mat-table [dataSource]="penaltyToIncomeAccountMappings.value" class="spa-table">
|
|
||||||
<ng-container matColumnDef="chargeId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let penaltyIncome">
|
|
||||||
{{ penaltyIncome.chargeId | find: penaltyData : 'id' : 'name' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="incomeAccountId">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let penaltyIncome">
|
|
||||||
{{ penaltyIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="actions">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let penaltyIncome; let i = index">
|
|
||||||
<button
|
|
||||||
mat-icon-button
|
|
||||||
color="primary"
|
|
||||||
(click)="edit('PenaltyIncome', penaltyToIncomeAccountMappings, i)"
|
|
||||||
>
|
|
||||||
<fa-icon icon="edit"></fa-icon>
|
|
||||||
</button>
|
|
||||||
<button mat-icon-button color="warn" (click)="delete(penaltyToIncomeAccountMappings, i)">
|
|
||||||
<fa-icon icon="trash"></fa-icon>
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+273
-88
@@ -6,124 +6,309 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
$primary: #1353a0;
|
|
||||||
$accent: #0ea5e9;
|
|
||||||
$border: #e2e8f0;
|
$border: #e2e8f0;
|
||||||
$gray-50: #f8f9fa;
|
$gray-50: #f8f9fa;
|
||||||
$gray-200: #e8eaed;
|
$gray-200: #e8eaed;
|
||||||
$gray-500: #9aa0a6;
|
$gray-500: #6b7280;
|
||||||
$gray-700: #5f6368;
|
$gray-700: #334155;
|
||||||
$gray-900: #202124;
|
$gray-900: #202124;
|
||||||
|
$primary: #1353a0;
|
||||||
|
|
||||||
.spa-section {
|
.spa-section {
|
||||||
border: 1px solid $border;
|
border: 1px solid $border;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
|
|
||||||
& + & {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 12px 24px;
|
|
||||||
background: $gray-50;
|
|
||||||
border-bottom: 1px solid $gray-200;
|
|
||||||
|
|
||||||
i {
|
|
||||||
font-size: 14px;
|
|
||||||
color: $accent;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: $gray-900;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.spa-body {
|
.spa-section__header {
|
||||||
padding: 16px 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
gap: 4px 16px;
|
|
||||||
padding: 16px 24px;
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-field {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-radio-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 24px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.spa-checkbox-row {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px 0;
|
gap: 10px;
|
||||||
margin-bottom: 12px;
|
padding: 12px 24px;
|
||||||
|
background: $gray-50;
|
||||||
|
border-bottom: 1px solid $gray-200;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spa-subsection {
|
.spa-section__icon {
|
||||||
margin-top: 16px;
|
display: inline-flex;
|
||||||
padding-top: 16px;
|
align-items: center;
|
||||||
border-top: 1px solid $gray-200;
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-section__body {
|
||||||
|
padding: 16px 24px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-group {
|
||||||
|
& + & {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
margin-bottom: 12px;
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin: 0;
|
margin: 0 0 12px;
|
||||||
font-size: 13px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 700;
|
||||||
color: $gray-700;
|
letter-spacing: 0.08em;
|
||||||
}
|
text-transform: uppercase;
|
||||||
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spa-add-btn {
|
.spa-add-btn {
|
||||||
height: 32px !important;
|
border: 0;
|
||||||
border-radius: 8px !important;
|
background: transparent;
|
||||||
font-size: 12px !important;
|
color: #2563eb;
|
||||||
font-weight: 600 !important;
|
font-size: 12px;
|
||||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
font-weight: 700;
|
||||||
white-space: nowrap;
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spa-table-wrap {
|
.spa-list {
|
||||||
border: 1px solid $border;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-list + .spa-list {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
background: #fff;
|
||||||
|
|
||||||
|
@media (width <= 900px) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spa-table {
|
.spa-row:has(mifosx-gl-account-selector) {
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row--other {
|
||||||
|
border-style: dashed;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
background: #f8fafc;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
|
||||||
|
@media (width <= 1100px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-row-wrap {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
@media (width <= 1100px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__meta--other {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $gray-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__input {
|
||||||
|
flex: 0 0 22rem;
|
||||||
|
max-width: 22rem;
|
||||||
|
min-width: 18rem;
|
||||||
|
|
||||||
|
@media (width <= 900px) {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__input--other {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__input :is(mifosx-gl-account-selector) {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__input :is(mifosx-gl-account-selector) :is(.layout-column) {
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__input :is(mifosx-gl-account-selector) :is(mat-form-field) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spa-row__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__actions--other {
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-self: stretch;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@media (width <= 1100px) {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__name {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 24rem;
|
||||||
|
height: 40px;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
@media (width <= 900px) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__name--code {
|
||||||
|
max-width: 20rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__name--other-account {
|
||||||
|
max-width: 100%;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__other-field {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__other-field :is(mat-form-field) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__other-field :is(.mat-mdc-form-field-required-marker) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-remove-btn {
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: #ef4444;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-remove-btn--side {
|
||||||
|
place-self: center end;
|
||||||
|
margin-right: 4px;
|
||||||
|
|
||||||
|
@media (width <= 1100px) {
|
||||||
|
justify-self: start;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&--green {
|
||||||
|
background: #dcfce7;
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--amber {
|
||||||
|
background: #fef3c7;
|
||||||
|
color: #d97706;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--blue {
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #2563eb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
datalist {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 900px) {
|
||||||
|
.spa-row__meta {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-row__input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+429
-423
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright since 2025 Mifos Initiative
|
* Copyright since 2025 Mifos Initiative
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
@@ -6,489 +6,495 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
import { Component, Input, OnInit, inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
UntypedFormGroup,
|
FormsModule,
|
||||||
UntypedFormBuilder,
|
ReactiveFormsModule,
|
||||||
UntypedFormArray,
|
UntypedFormArray,
|
||||||
Validators,
|
UntypedFormBuilder,
|
||||||
UntypedFormControl,
|
UntypedFormControl,
|
||||||
ReactiveFormsModule
|
UntypedFormGroup,
|
||||||
|
Validators
|
||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
|
||||||
|
import { GlAccountSelectorComponent } from 'app/shared/accounting/gl-account-selector/gl-account-selector.component';
|
||||||
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
|
import { Accounting } from 'app/core/utils/accounting';
|
||||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
import { GLAccount } from 'app/shared/models/general.model';
|
||||||
|
|
||||||
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
|
|
||||||
import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
|
||||||
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
|
||||||
import { GlAccountSelectorComponent } from '../../../../shared/accounting/gl-account-selector/gl-account-selector.component';
|
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
|
||||||
import { MatButton, MatIconButton } from '@angular/material/button';
|
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
||||||
import {
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow
|
|
||||||
} from '@angular/material/table';
|
|
||||||
import { FindPipe } from '../../../../pipes/find.pipe';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income';
|
||||||
|
|
||||||
|
type AccountingRowConfig = {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
inputLabel: string;
|
||||||
|
controlName: string;
|
||||||
|
options: GLAccount[];
|
||||||
|
required?: boolean;
|
||||||
|
accrualOnly?: boolean;
|
||||||
|
dormancyOnly?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AccountingSectionConfig = {
|
||||||
|
key: AccountingSectionKey;
|
||||||
|
title: string;
|
||||||
|
pillLabel: string;
|
||||||
|
pillClass: 'green' | 'amber' | 'blue';
|
||||||
|
rows: AccountingRowConfig[];
|
||||||
|
otherMappingsControlName: string;
|
||||||
|
otherAccountOptions: GLAccount[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type OtherGlMappingValue = {
|
||||||
|
accountName: string;
|
||||||
|
glAccountId: number | null;
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'mifosx-saving-product-accounting-step',
|
selector: 'mifosx-saving-product-accounting-step',
|
||||||
templateUrl: './saving-product-accounting-step.component.html',
|
templateUrl: './saving-product-accounting-step.component.html',
|
||||||
styleUrls: ['./saving-product-accounting-step.component.scss'],
|
styleUrls: ['./saving-product-accounting-step.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
MatRadioGroup,
|
MatRadioGroup,
|
||||||
MatRadioButton,
|
MatRadioButton,
|
||||||
GlAccountSelectorComponent,
|
GlAccountSelectorComponent
|
||||||
MatCheckbox,
|
|
||||||
FaIconComponent,
|
|
||||||
MatTable,
|
|
||||||
MatColumnDef,
|
|
||||||
MatHeaderCellDef,
|
|
||||||
MatHeaderCell,
|
|
||||||
MatCellDef,
|
|
||||||
MatCell,
|
|
||||||
MatIconButton,
|
|
||||||
MatHeaderRowDef,
|
|
||||||
MatHeaderRow,
|
|
||||||
MatRowDef,
|
|
||||||
MatRow,
|
|
||||||
FindPipe
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SavingProductAccountingStepComponent implements OnInit {
|
export class SavingProductAccountingStepComponent implements OnInit {
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
private dialog = inject(MatDialog);
|
private accounting = inject(Accounting);
|
||||||
private translateService = inject(TranslateService);
|
|
||||||
|
|
||||||
@Input() savingProductsTemplate: any;
|
@Input() savingProductsTemplate: any;
|
||||||
@Input() accountingRuleData: any;
|
@Input() accountingRuleData: any;
|
||||||
@Input() isDormancyTrackingActive: UntypedFormControl;
|
@Input() isDormancyTrackingActive: any;
|
||||||
@Input() savingProductFormValid: boolean;
|
@Input() savingProductFormValid: boolean;
|
||||||
@Input() allowOverdraft: UntypedFormControl;
|
@Input() allowOverdraft: any;
|
||||||
|
|
||||||
savingProductAccountingForm: UntypedFormGroup;
|
savingProductAccountingForm: UntypedFormGroup;
|
||||||
|
liabilityAccountOptions: GLAccount[] = [];
|
||||||
chargeData: any;
|
expenseAccountOptions: GLAccount[] = [];
|
||||||
penaltyData: any;
|
incomeAccountOptions: GLAccount[] = [];
|
||||||
paymentTypeData: any;
|
assetAccountOptions: GLAccount[] = [];
|
||||||
assetAccountData: any;
|
accountingSectionConfigs: AccountingSectionConfig[] = [];
|
||||||
incomeAccountData: any;
|
|
||||||
expenseAccountData: any;
|
|
||||||
liabilityAccountData: any;
|
|
||||||
incomeAndLiabilityAccountData: any;
|
|
||||||
combinedAccountData: any[];
|
|
||||||
|
|
||||||
paymentFundSourceDisplayedColumns: string[] = [
|
|
||||||
'paymentTypeId',
|
|
||||||
'fundSourceAccountId',
|
|
||||||
'actions'
|
|
||||||
];
|
|
||||||
feesPenaltyIncomeDisplayedColumns: string[] = [
|
|
||||||
'chargeId',
|
|
||||||
'incomeAccountId',
|
|
||||||
'actions'
|
|
||||||
];
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.createsavingProductAccountingForm();
|
this.createSavingProductAccountingForm();
|
||||||
this.setConditionalControls();
|
this.setConditionalControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.chargeData = this.savingProductsTemplate.chargeOptions || [];
|
const mappings = this.savingProductsTemplate?.accountingMappings || {};
|
||||||
this.penaltyData = this.savingProductsTemplate.penaltyOptions || [];
|
const accountingMappingOptions = this.savingProductsTemplate?.accountingMappingOptions || {};
|
||||||
this.paymentTypeData = this.savingProductsTemplate.paymentTypeOptions || [];
|
const accountingRuleId = this.savingProductsTemplate?.accountingRule?.id || 1;
|
||||||
this.assetAccountData = this.savingProductsTemplate.accountingMappingOptions.assetAccountOptions || [];
|
this.assetAccountOptions = accountingMappingOptions.assetAccountOptions || [];
|
||||||
this.incomeAccountData = this.savingProductsTemplate.accountingMappingOptions.incomeAccountOptions || [];
|
this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || [];
|
||||||
this.expenseAccountData = this.savingProductsTemplate.accountingMappingOptions.expenseAccountOptions || [];
|
this.expenseAccountOptions = accountingMappingOptions.expenseAccountOptions || [];
|
||||||
this.liabilityAccountData = this.savingProductsTemplate.accountingMappingOptions.liabilityAccountOptions || [];
|
this.incomeAccountOptions = accountingMappingOptions.incomeAccountOptions || [];
|
||||||
this.combinedAccountData = [
|
this.buildAccountingSectionConfigs();
|
||||||
...this.assetAccountData,
|
this.savingProductAccountingForm.patchValue({
|
||||||
...this.incomeAccountData,
|
accountingRule: accountingRuleId
|
||||||
...this.expenseAccountData,
|
|
||||||
...this.liabilityAccountData
|
|
||||||
];
|
|
||||||
this.combinedAccountData.sort((a, b) => {
|
|
||||||
const valueA = a.name.toLowerCase();
|
|
||||||
const valueB = b.name.toLowerCase();
|
|
||||||
if (valueA < valueB) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (valueA > valueB) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.syncAccountingControls(accountingRuleId);
|
||||||
|
this.patchAccountingValues(mappings);
|
||||||
|
this.setOverdraftControl();
|
||||||
|
this.setDormancyControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
createSavingProductAccountingForm() {
|
||||||
|
this.savingProductAccountingForm = this.formBuilder.group({
|
||||||
|
accountingRule: [1],
|
||||||
|
assetsOtherGlMappings: this.formBuilder.array([]),
|
||||||
|
liabilitiesOtherGlMappings: this.formBuilder.array([]),
|
||||||
|
expensesOtherGlMappings: this.formBuilder.array([]),
|
||||||
|
incomeOtherGlMappings: this.formBuilder.array([])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setConditionalControls() {
|
||||||
|
this.savingProductAccountingForm.get('accountingRule')?.valueChanges.subscribe((accountingRule: number) => {
|
||||||
|
this.syncAccountingControls(accountingRule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private patchAccountingValues(mappings: any) {
|
||||||
this.savingProductAccountingForm.patchValue({
|
this.savingProductAccountingForm.patchValue({
|
||||||
accountingRule: this.savingProductsTemplate.accountingRule.id
|
savingsReferenceAccountId: mappings.savingsReferenceAccount?.id || '',
|
||||||
|
savingsControlAccountId: mappings.savingsControlAccount?.id || '',
|
||||||
|
transfersInSuspenseAccountId: mappings.transfersInSuspenseAccount?.id || '',
|
||||||
|
interestOnSavingsAccountId: mappings.interestOnSavingsAccount?.id || '',
|
||||||
|
incomeFromFeeAccountId: mappings.incomeFromFeeAccount?.id || '',
|
||||||
|
incomeFromPenaltyAccountId: mappings.incomeFromPenaltyAccount?.id || '',
|
||||||
|
incomeFromInterestId: mappings.incomeFromInterest?.id || '',
|
||||||
|
overdraftPortfolioControlId: mappings.overdraftPortfolioControl?.id || '',
|
||||||
|
writeOffAccountId: mappings.writeOffAccount?.id || '',
|
||||||
|
feesReceivableAccountId: mappings.feeReceivableAccount?.id || '',
|
||||||
|
penaltiesReceivableAccountId: mappings.penaltyReceivableAccount?.id || '',
|
||||||
|
interestPayableAccountId: mappings.interestPayableAccount?.id || '',
|
||||||
|
interestReceivableAccountId: mappings.interestReceivableAccount?.id || '',
|
||||||
|
escheatLiabilityId: mappings.escheatLiabilityAccount?.id || this.liabilityAccountOptions[0]?.id || ''
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.isCashOrAccrualAccounting()) {
|
if (this.isCashOrAccrualAccounting()) {
|
||||||
this.savingProductAccountingForm.patchValue({
|
this.setOtherGlMappings(
|
||||||
savingsReferenceAccountId: this.savingProductsTemplate.accountingMappings.savingsReferenceAccount.id,
|
'assetsOtherGlMappings',
|
||||||
overdraftPortfolioControlId: this.savingProductsTemplate.accountingMappings.overdraftPortfolioControl.id,
|
mappings.otherGlMappings?.assets || [],
|
||||||
savingsControlAccountId: this.savingProductsTemplate.accountingMappings.savingsControlAccount.id,
|
this.assetAccountOptions
|
||||||
transfersInSuspenseAccountId: this.savingProductsTemplate.accountingMappings.transfersInSuspenseAccount.id,
|
|
||||||
interestOnSavingsAccountId: this.savingProductsTemplate.accountingMappings.interestOnSavingsAccount.id,
|
|
||||||
writeOffAccountId: this.savingProductsTemplate.accountingMappings.writeOffAccount.id,
|
|
||||||
incomeFromFeeAccountId: this.savingProductsTemplate.accountingMappings.incomeFromFeeAccount.id,
|
|
||||||
incomeFromPenaltyAccountId: this.savingProductsTemplate.accountingMappings.incomeFromPenaltyAccount.id,
|
|
||||||
incomeFromInterestId: this.savingProductsTemplate.accountingMappings.incomeFromInterest.id,
|
|
||||||
advancedAccountingRules:
|
|
||||||
this.savingProductsTemplate.paymentChannelToFundSourceMappings ||
|
|
||||||
this.savingProductsTemplate.feeToIncomeAccountMappings ||
|
|
||||||
this.savingProductsTemplate.penaltyToIncomeAccountMappings ||
|
|
||||||
this.savingProductsTemplate.accrualCharges
|
|
||||||
? true
|
|
||||||
: false
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.isAccrualAccounting()) {
|
|
||||||
this.savingProductAccountingForm.patchValue({
|
|
||||||
feesReceivableAccountId: this.savingProductsTemplate.accountingMappings.feeReceivableAccount.id,
|
|
||||||
penaltiesReceivableAccountId: this.savingProductsTemplate.accountingMappings.penaltyReceivableAccount.id,
|
|
||||||
interestReceivableAccountId:
|
|
||||||
this.savingProductsTemplate.accountingMappings.interestReceivableAccount == null || undefined
|
|
||||||
? ''
|
|
||||||
: this.savingProductsTemplate.accountingMappings.interestReceivableAccount.id,
|
|
||||||
interestPayableAccountId: this.savingProductsTemplate.accountingMappings.interestPayableAccount.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isDormancyTrackingActive.value) {
|
|
||||||
this.savingProductAccountingForm.patchValue({
|
|
||||||
escheatLiabilityId: this.savingProductsTemplate.accountingMappings.escheatLiabilityAccount.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.savingProductAccountingForm.setControl(
|
|
||||||
'paymentChannelToFundSourceMappings',
|
|
||||||
this.formBuilder.array(
|
|
||||||
(this.savingProductsTemplate.paymentChannelToFundSourceMappings || []).map((paymentFundSource: any) => ({
|
|
||||||
paymentTypeId: paymentFundSource.paymentType.id,
|
|
||||||
fundSourceAccountId: paymentFundSource.fundSourceAccount.id
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
this.savingProductAccountingForm.setControl(
|
this.setOtherGlMappings(
|
||||||
'feeToIncomeAccountMappings',
|
'liabilitiesOtherGlMappings',
|
||||||
this.formBuilder.array(
|
mappings.otherGlMappings?.liabilities || [],
|
||||||
(this.savingProductsTemplate.feeToIncomeAccountMappings || []).map((feesIncome: any) => ({
|
this.liabilityAccountOptions
|
||||||
chargeId: feesIncome.charge.id,
|
|
||||||
incomeAccountId: feesIncome.incomeAccount.id
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
this.savingProductAccountingForm.setControl(
|
this.setOtherGlMappings(
|
||||||
'penaltyToIncomeAccountMappings',
|
'expensesOtherGlMappings',
|
||||||
this.formBuilder.array(
|
mappings.otherGlMappings?.expenses || [],
|
||||||
(this.savingProductsTemplate.penaltyToIncomeAccountMappings || []).map((penaltyIncome: any) => ({
|
this.expenseAccountOptions
|
||||||
chargeId: penaltyIncome.charge.id,
|
);
|
||||||
incomeAccountId: penaltyIncome.incomeAccount.id
|
this.setOtherGlMappings(
|
||||||
}))
|
'incomeOtherGlMappings',
|
||||||
)
|
mappings.otherGlMappings?.income || [],
|
||||||
|
this.incomeAccountOptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createsavingProductAccountingForm() {
|
private createOtherGlMappingGroup(otherGlMapping: Partial<OtherGlMappingValue> = {}) {
|
||||||
this.savingProductAccountingForm = this.formBuilder.group({
|
return this.formBuilder.group({
|
||||||
accountingRule: [1]
|
accountName: [
|
||||||
|
otherGlMapping.accountName || '',
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
glAccountId: [
|
||||||
|
otherGlMapping.glAccountId || '',
|
||||||
|
Validators.required
|
||||||
|
]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setConditionalControls() {
|
getOtherGlMappingsArray(controlName: string): UntypedFormArray {
|
||||||
this.savingProductAccountingForm.get('accountingRule').valueChanges.subscribe((accountingRule: any) => {
|
return this.savingProductAccountingForm.get(controlName) as UntypedFormArray;
|
||||||
if (accountingRule === 2 || accountingRule === 3) {
|
}
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'savingsReferenceAccountId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'overdraftPortfolioControlId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'savingsControlAccountId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'transfersInSuspenseAccountId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'interestOnSavingsAccountId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'writeOffAccountId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'incomeFromFeeAccountId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'incomeFromPenaltyAccountId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'incomeFromInterestId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
this.savingProductAccountingForm.addControl('advancedAccountingRules', new UntypedFormControl(false));
|
|
||||||
|
|
||||||
if (accountingRule === 3) {
|
private setOtherGlMappings(controlName: string, otherGlMappings: any[], options: GLAccount[]) {
|
||||||
this.savingProductAccountingForm.addControl(
|
const otherGlMappingsArray = this.getOtherGlMappingsArray(controlName);
|
||||||
'feesReceivableAccountId',
|
otherGlMappingsArray.clear();
|
||||||
new UntypedFormControl('', Validators.required)
|
(otherGlMappings || []).forEach((otherGlMapping) => {
|
||||||
);
|
otherGlMappingsArray.push(
|
||||||
this.savingProductAccountingForm.addControl(
|
this.createOtherGlMappingGroup({
|
||||||
'penaltiesReceivableAccountId',
|
accountName: otherGlMapping.accountName,
|
||||||
new UntypedFormControl('', Validators.required)
|
glAccountId:
|
||||||
);
|
otherGlMapping.glAccountId || options.find((option) => option.glCode === otherGlMapping.glCode)?.id || null
|
||||||
if (this.allowOverdraft.value) {
|
})
|
||||||
this.savingProductAccountingForm.addControl('interestReceivableAccountId', new UntypedFormControl(''));
|
);
|
||||||
}
|
});
|
||||||
this.allowOverdraft.valueChanges.subscribe((allowOverdraft: boolean) => {
|
}
|
||||||
if (allowOverdraft) {
|
|
||||||
this.savingProductAccountingForm.addControl('interestReceivableAccountId', new UntypedFormControl(''));
|
|
||||||
} else {
|
|
||||||
this.savingProductAccountingForm.removeControl('interestReceivableAccountId');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.savingProductAccountingForm.addControl(
|
|
||||||
'interestPayableAccountId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (accountingRule === 2) {
|
|
||||||
this.savingProductAccountingForm.removeControl('feesReceivableAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('penaltiesReceivableAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('interestPayableAccountId');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isDormancyTrackingActive.value) {
|
addOtherGlMapping(controlName: string) {
|
||||||
this.savingProductAccountingForm.addControl(
|
this.getOtherGlMappingsArray(controlName).push(this.createOtherGlMappingGroup());
|
||||||
'escheatLiabilityId',
|
}
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isDormancyTrackingActive.valueChanges.subscribe((isDormancyTrackingActive: boolean) => {
|
removeOtherGlMapping(controlName: string, index: number) {
|
||||||
if (isDormancyTrackingActive) {
|
this.getOtherGlMappingsArray(controlName).removeAt(index);
|
||||||
this.savingProductAccountingForm.addControl(
|
}
|
||||||
'escheatLiabilityId',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.savingProductAccountingForm.removeControl('escheatLiabilityId');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.savingProductAccountingForm
|
getOtherGlMappingControl(controlName: string, index: number, fieldName: string): UntypedFormControl {
|
||||||
.get('advancedAccountingRules')
|
return this.getOtherGlMappingsArray(controlName).at(index).get(fieldName) as UntypedFormControl;
|
||||||
.valueChanges.subscribe((advancedAccountingRules: boolean) => {
|
}
|
||||||
if (advancedAccountingRules) {
|
|
||||||
this.savingProductAccountingForm.addControl(
|
private clearOtherGlMappings() {
|
||||||
'paymentChannelToFundSourceMappings',
|
[
|
||||||
this.formBuilder.array([])
|
'assetsOtherGlMappings',
|
||||||
);
|
'liabilitiesOtherGlMappings',
|
||||||
this.savingProductAccountingForm.addControl('feeToIncomeAccountMappings', this.formBuilder.array([]));
|
'expensesOtherGlMappings',
|
||||||
this.savingProductAccountingForm.addControl('penaltyToIncomeAccountMappings', this.formBuilder.array([]));
|
'incomeOtherGlMappings'
|
||||||
} else {
|
].forEach((controlName) => this.getOtherGlMappingsArray(controlName).clear());
|
||||||
this.savingProductAccountingForm.removeControl('paymentChannelToFundSourceMappings');
|
}
|
||||||
this.savingProductAccountingForm.removeControl('feeToIncomeAccountMappings');
|
|
||||||
this.savingProductAccountingForm.removeControl('penaltyToIncomeAccountMappings');
|
private setOverdraftControl() {
|
||||||
}
|
this.allowOverdraft?.valueChanges?.subscribe(() => {
|
||||||
});
|
this.syncAccountingControls(this.savingProductAccountingForm.value.accountingRule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private setDormancyControl() {
|
||||||
|
this.isDormancyTrackingActive?.valueChanges?.subscribe(() => {
|
||||||
|
this.syncAccountingControls(this.savingProductAccountingForm.value.accountingRule);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getOtherGlMappings() {
|
||||||
|
const mapOtherGlMappings = (controlName: string, options: GLAccount[]) =>
|
||||||
|
this.getOtherGlMappingsArray(controlName)
|
||||||
|
.value.filter((item: OtherGlMappingValue) => !!item.accountName || !!item.glAccountId)
|
||||||
|
.map((item: OtherGlMappingValue) => ({
|
||||||
|
accountName: item.accountName,
|
||||||
|
glCode: options.find((option) => option.id === item.glAccountId)?.glCode || ''
|
||||||
|
}))
|
||||||
|
.filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode);
|
||||||
|
|
||||||
|
const otherGlMappings = {
|
||||||
|
assets: mapOtherGlMappings('assetsOtherGlMappings', this.assetAccountOptions),
|
||||||
|
liabilities: mapOtherGlMappings('liabilitiesOtherGlMappings', this.liabilityAccountOptions),
|
||||||
|
expenses: mapOtherGlMappings('expensesOtherGlMappings', this.expenseAccountOptions),
|
||||||
|
income: mapOtherGlMappings('incomeOtherGlMappings', this.incomeAccountOptions)
|
||||||
|
};
|
||||||
|
|
||||||
|
return Object.values(otherGlMappings).some((value) => value.length) ? otherGlMappings : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
isDormancyTrackingEnabled(): boolean {
|
||||||
|
return this.isDormancyTrackingActive?.value ?? this.isDormancyTrackingActive ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private syncAccountingControls(accountingRule: number) {
|
||||||
|
const isDormancyActive = this.isDormancyTrackingEnabled();
|
||||||
|
|
||||||
|
const addControl = (controlName: string, required = false) => {
|
||||||
|
if (!this.savingProductAccountingForm.contains(controlName)) {
|
||||||
|
this.savingProductAccountingForm.addControl(
|
||||||
|
controlName,
|
||||||
|
new UntypedFormControl('', required ? Validators.required : undefined)
|
||||||
|
);
|
||||||
|
} else if (required) {
|
||||||
|
this.savingProductAccountingForm.get(controlName)?.setValidators(Validators.required);
|
||||||
|
this.savingProductAccountingForm.get(controlName)?.updateValueAndValidity({ emitEvent: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeControl = (controlName: string) => {
|
||||||
|
if (this.savingProductAccountingForm.contains(controlName)) {
|
||||||
|
this.savingProductAccountingForm.removeControl(controlName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.accounting.isCashOrAccrualAccountingRuleId(accountingRule)) {
|
||||||
|
addControl('savingsReferenceAccountId', true);
|
||||||
|
addControl('savingsControlAccountId', true);
|
||||||
|
addControl('transfersInSuspenseAccountId', true);
|
||||||
|
addControl('interestOnSavingsAccountId', true);
|
||||||
|
addControl('incomeFromFeeAccountId', true);
|
||||||
|
addControl('incomeFromPenaltyAccountId', true);
|
||||||
|
addControl('incomeFromInterestId', true);
|
||||||
|
addControl('writeOffAccountId', true);
|
||||||
|
addControl('overdraftPortfolioControlId', false);
|
||||||
|
|
||||||
|
if (this.accounting.isAccrualAccountingRuleId(accountingRule)) {
|
||||||
|
addControl('feesReceivableAccountId', true);
|
||||||
|
addControl('penaltiesReceivableAccountId', true);
|
||||||
|
addControl('interestPayableAccountId', true);
|
||||||
|
addControl('interestReceivableAccountId', false);
|
||||||
} else {
|
} else {
|
||||||
this.savingProductAccountingForm.removeControl('savingsReferenceAccountId');
|
removeControl('feesReceivableAccountId');
|
||||||
this.savingProductAccountingForm.removeControl('overdraftPortfolioControlId');
|
removeControl('penaltiesReceivableAccountId');
|
||||||
this.savingProductAccountingForm.removeControl('savingsControlAccountId');
|
removeControl('interestPayableAccountId');
|
||||||
this.savingProductAccountingForm.removeControl('transfersInSuspenseAccountId');
|
removeControl('interestReceivableAccountId');
|
||||||
this.savingProductAccountingForm.removeControl('interestOnSavingsAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('writeOffAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('incomeFromFeeAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('incomeFromPenaltyAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('incomeFromInterestId');
|
|
||||||
this.savingProductAccountingForm.removeControl('advancedAccountingRules');
|
|
||||||
this.savingProductAccountingForm.removeControl('escheatLiabilityId');
|
|
||||||
this.savingProductAccountingForm.removeControl('feesReceivableAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('penaltiesReceivableAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('interestReceivableAccountId');
|
|
||||||
this.savingProductAccountingForm.removeControl('interestPayableAccountId');
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
get paymentChannelToFundSourceMappings(): UntypedFormArray {
|
if (isDormancyActive) {
|
||||||
return this.savingProductAccountingForm.get('paymentChannelToFundSourceMappings') as UntypedFormArray;
|
addControl('escheatLiabilityId', false);
|
||||||
}
|
} else {
|
||||||
|
removeControl('escheatLiabilityId');
|
||||||
get feeToIncomeAccountMappings(): UntypedFormArray {
|
}
|
||||||
return this.savingProductAccountingForm.get('feeToIncomeAccountMappings') as UntypedFormArray;
|
} else {
|
||||||
}
|
removeControl('savingsReferenceAccountId');
|
||||||
|
removeControl('savingsControlAccountId');
|
||||||
get penaltyToIncomeAccountMappings(): UntypedFormArray {
|
removeControl('transfersInSuspenseAccountId');
|
||||||
return this.savingProductAccountingForm.get('penaltyToIncomeAccountMappings') as UntypedFormArray;
|
removeControl('interestOnSavingsAccountId');
|
||||||
}
|
removeControl('incomeFromFeeAccountId');
|
||||||
|
removeControl('incomeFromPenaltyAccountId');
|
||||||
setSavingProductAccountingFormDirty() {
|
removeControl('incomeFromInterestId');
|
||||||
if (this.savingProductAccountingForm.pristine) {
|
removeControl('writeOffAccountId');
|
||||||
this.savingProductAccountingForm.markAsDirty();
|
removeControl('overdraftPortfolioControlId');
|
||||||
|
removeControl('feesReceivableAccountId');
|
||||||
|
removeControl('penaltiesReceivableAccountId');
|
||||||
|
removeControl('interestPayableAccountId');
|
||||||
|
removeControl('interestReceivableAccountId');
|
||||||
|
removeControl('escheatLiabilityId');
|
||||||
|
this.clearOtherGlMappings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
existCharges(): boolean {
|
|
||||||
return this.chargeData.length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
add(formType: string, formArray: UntypedFormArray) {
|
|
||||||
const data = { ...this.getData(formType), pristine: false };
|
|
||||||
const dialogRef = this.dialog.open(FormDialogComponent, { data, width: '20rem' });
|
|
||||||
dialogRef.afterClosed().subscribe((response: any) => {
|
|
||||||
if (response.data) {
|
|
||||||
formArray.push(response.data);
|
|
||||||
this.setSavingProductAccountingFormDirty();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
edit(formType: string, formArray: UntypedFormArray, index: number) {
|
|
||||||
const data = { ...this.getData(formType, formArray.at(index).value), layout: { addButtonText: 'Edit' } };
|
|
||||||
const dialogRef = this.dialog.open(FormDialogComponent, { data });
|
|
||||||
dialogRef.afterClosed().subscribe((response: any) => {
|
|
||||||
if (response.data) {
|
|
||||||
formArray.at(index).patchValue(response.data.value);
|
|
||||||
this.setSavingProductAccountingFormDirty();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(formArray: UntypedFormArray, index: number) {
|
|
||||||
const dialogRef = this.dialog.open(DeleteDialogComponent, {
|
|
||||||
data: { deleteContext: `this` }
|
|
||||||
});
|
|
||||||
dialogRef.afterClosed().subscribe((response: any) => {
|
|
||||||
if (response.delete) {
|
|
||||||
formArray.removeAt(index);
|
|
||||||
this.setSavingProductAccountingFormDirty();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getData(formType: string, values?: any) {
|
|
||||||
switch (formType) {
|
|
||||||
case 'PaymentFundSource':
|
|
||||||
return {
|
|
||||||
title: this.translateService.instant('labels.heading.Configure Fund Sources for Payment Channels'),
|
|
||||||
formfields: this.getPaymentFundSourceFormfields(values)
|
|
||||||
};
|
|
||||||
case 'FeesIncome':
|
|
||||||
return {
|
|
||||||
title: this.translateService.instant('labels.heading.Map Fees to Specific Income Accounts'),
|
|
||||||
formfields: this.getFeesIncomeFormfields(values)
|
|
||||||
};
|
|
||||||
case 'PenaltyIncome':
|
|
||||||
return {
|
|
||||||
title: this.translateService.instant('labels.heading.Map Penalties to Specific Income Accounts'),
|
|
||||||
formfields: this.getPenaltyIncomeFormfields(values)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getPaymentFundSourceFormfields(values?: any) {
|
|
||||||
const formfields: FormfieldBase[] = [
|
|
||||||
new SelectBase({
|
|
||||||
controlName: 'paymentTypeId',
|
|
||||||
label: this.translateService.instant('labels.inputs.Payment Type'),
|
|
||||||
value: values ? values.paymentTypeId : this.paymentTypeData[0].id,
|
|
||||||
options: { label: 'name', value: 'id', data: this.paymentTypeData },
|
|
||||||
required: true,
|
|
||||||
order: 1
|
|
||||||
}),
|
|
||||||
new SelectBase({
|
|
||||||
controlName: 'fundSourceAccountId',
|
|
||||||
label: this.translateService.instant('labels.inputs.Fund Source'),
|
|
||||||
value: values ? values.fundSourceAccountId : this.combinedAccountData[0].id,
|
|
||||||
options: { label: 'name', value: 'id', data: this.combinedAccountData },
|
|
||||||
required: true,
|
|
||||||
order: 2
|
|
||||||
})
|
|
||||||
];
|
|
||||||
return formfields;
|
|
||||||
}
|
|
||||||
|
|
||||||
getFeesIncomeFormfields(values?: any) {
|
|
||||||
const formfields: FormfieldBase[] = [
|
|
||||||
new SelectBase({
|
|
||||||
controlName: 'chargeId',
|
|
||||||
label: this.translateService.instant('labels.inputs.Fees'),
|
|
||||||
value: values ? values.chargeId : this.chargeData[0].id,
|
|
||||||
options: { label: 'name', value: 'id', data: this.chargeData },
|
|
||||||
required: true,
|
|
||||||
order: 1
|
|
||||||
}),
|
|
||||||
new SelectBase({
|
|
||||||
controlName: 'incomeAccountId',
|
|
||||||
label: this.translateService.instant('labels.inputs.Income Account'),
|
|
||||||
value: values ? values.incomeAccountId : this.incomeAccountData[0].id,
|
|
||||||
options: { label: 'name', value: 'id', data: this.incomeAccountData },
|
|
||||||
required: true,
|
|
||||||
order: 2
|
|
||||||
})
|
|
||||||
];
|
|
||||||
return formfields;
|
|
||||||
}
|
|
||||||
|
|
||||||
getPenaltyIncomeFormfields(values?: any) {
|
|
||||||
const formfields: FormfieldBase[] = [
|
|
||||||
new SelectBase({
|
|
||||||
controlName: 'chargeId',
|
|
||||||
label: this.translateService.instant('labels.inputs.Penalty'),
|
|
||||||
value: values ? values.chargeId : this.penaltyData[0].id,
|
|
||||||
options: { label: 'name', value: 'id', data: this.penaltyData },
|
|
||||||
required: true,
|
|
||||||
order: 1
|
|
||||||
}),
|
|
||||||
new SelectBase({
|
|
||||||
controlName: 'incomeAccountId',
|
|
||||||
label: this.translateService.instant('labels.inputs.Income Account'),
|
|
||||||
value: values ? values.incomeAccountId : this.incomeAccountData[0].id,
|
|
||||||
options: { label: 'name', value: 'id', data: this.incomeAccountData },
|
|
||||||
required: true,
|
|
||||||
order: 2
|
|
||||||
})
|
|
||||||
];
|
|
||||||
return formfields;
|
|
||||||
}
|
|
||||||
|
|
||||||
get savingProductAccounting() {
|
get savingProductAccounting() {
|
||||||
return this.savingProductAccountingForm.value;
|
const {
|
||||||
|
assetsOtherGlMappings,
|
||||||
|
liabilitiesOtherGlMappings,
|
||||||
|
expensesOtherGlMappings,
|
||||||
|
incomeOtherGlMappings,
|
||||||
|
...savingProductAccounting
|
||||||
|
} = this.savingProductAccountingForm.value;
|
||||||
|
const otherGlMappings = this.getOtherGlMappings();
|
||||||
|
|
||||||
|
return {
|
||||||
|
...savingProductAccounting,
|
||||||
|
...(otherGlMappings ? { otherGlMappings } : {})
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
isCashOrAccrualAccounting(): boolean {
|
isCashOrAccrualAccounting(): boolean {
|
||||||
return (
|
return this.accounting.isCashOrAccrualAccountingRuleId(this.savingProductAccountingForm.value.accountingRule);
|
||||||
this.savingProductAccountingForm.value.accountingRule === 2 ||
|
|
||||||
this.savingProductAccountingForm.value.accountingRule === 3
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isAccrualAccounting(): boolean {
|
isAccrualAccounting(): boolean {
|
||||||
return this.savingProductAccountingForm.value.accountingRule === 3;
|
return this.accounting.isAccrualAccountingRuleId(this.savingProductAccountingForm.value.accountingRule);
|
||||||
|
}
|
||||||
|
|
||||||
|
buildAccountingSectionConfigs() {
|
||||||
|
this.accountingSectionConfigs = [
|
||||||
|
{
|
||||||
|
key: 'assets',
|
||||||
|
title: 'Assets',
|
||||||
|
pillLabel: 'Asset',
|
||||||
|
pillClass: 'green',
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
key: 'savingsReferenceAccountId',
|
||||||
|
label: 'Savings Reference Account',
|
||||||
|
inputLabel: 'Savings reference account',
|
||||||
|
controlName: 'savingsReferenceAccountId',
|
||||||
|
options: this.assetAccountOptions,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'overdraftPortfolioControlId',
|
||||||
|
label: 'Overdraft Portfolio Control',
|
||||||
|
inputLabel: 'Overdraft portfolio control',
|
||||||
|
controlName: 'overdraftPortfolioControlId',
|
||||||
|
options: this.assetAccountOptions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'interestReceivableAccountId',
|
||||||
|
label: 'Interest Receivable Account',
|
||||||
|
inputLabel: 'Interest receivable account',
|
||||||
|
controlName: 'interestReceivableAccountId',
|
||||||
|
options: this.assetAccountOptions,
|
||||||
|
accrualOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'feesReceivableAccountId',
|
||||||
|
label: 'Fees Receivable Account',
|
||||||
|
inputLabel: 'Fees receivable account',
|
||||||
|
controlName: 'feesReceivableAccountId',
|
||||||
|
options: this.assetAccountOptions,
|
||||||
|
accrualOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'penaltiesReceivableAccountId',
|
||||||
|
label: 'Penalties Receivable Account',
|
||||||
|
inputLabel: 'Penalties receivable account',
|
||||||
|
controlName: 'penaltiesReceivableAccountId',
|
||||||
|
options: this.assetAccountOptions,
|
||||||
|
accrualOnly: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
otherMappingsControlName: 'assetsOtherGlMappings',
|
||||||
|
otherAccountOptions: this.assetAccountOptions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'liabilities',
|
||||||
|
title: 'Liabilities',
|
||||||
|
pillLabel: 'Liability',
|
||||||
|
pillClass: 'green',
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
key: 'savingsControlAccountId',
|
||||||
|
label: 'Savings Control Account',
|
||||||
|
inputLabel: 'Savings control account',
|
||||||
|
controlName: 'savingsControlAccountId',
|
||||||
|
options: this.liabilityAccountOptions,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'transfersInSuspenseAccountId',
|
||||||
|
label: 'Transfers In Suspense Account',
|
||||||
|
inputLabel: 'Transfers in suspense account',
|
||||||
|
controlName: 'transfersInSuspenseAccountId',
|
||||||
|
options: this.liabilityAccountOptions,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'interestPayableAccountId',
|
||||||
|
label: 'Interest Payable Account',
|
||||||
|
inputLabel: 'Interest payable account',
|
||||||
|
controlName: 'interestPayableAccountId',
|
||||||
|
options: this.liabilityAccountOptions,
|
||||||
|
accrualOnly: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'escheatLiabilityId',
|
||||||
|
label: 'Escheat Liability Account',
|
||||||
|
inputLabel: 'Escheat liability account',
|
||||||
|
controlName: 'escheatLiabilityId',
|
||||||
|
options: this.liabilityAccountOptions,
|
||||||
|
dormancyOnly: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
otherMappingsControlName: 'liabilitiesOtherGlMappings',
|
||||||
|
otherAccountOptions: this.liabilityAccountOptions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'expenses',
|
||||||
|
title: 'Expenses',
|
||||||
|
pillLabel: 'Expense',
|
||||||
|
pillClass: 'amber',
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
key: 'interestOnSavingsAccountId',
|
||||||
|
label: 'Interest On Savings Account',
|
||||||
|
inputLabel: 'Interest on savings account',
|
||||||
|
controlName: 'interestOnSavingsAccountId',
|
||||||
|
options: this.expenseAccountOptions,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'writeOffAccountId',
|
||||||
|
label: 'Write Off Account',
|
||||||
|
inputLabel: 'Write off account',
|
||||||
|
controlName: 'writeOffAccountId',
|
||||||
|
options: this.expenseAccountOptions,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
otherMappingsControlName: 'expensesOtherGlMappings',
|
||||||
|
otherAccountOptions: this.expenseAccountOptions
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'income',
|
||||||
|
title: 'Income',
|
||||||
|
pillLabel: 'Income',
|
||||||
|
pillClass: 'blue',
|
||||||
|
rows: [
|
||||||
|
{
|
||||||
|
key: 'incomeFromFeeAccountId',
|
||||||
|
label: 'Income From Fees',
|
||||||
|
inputLabel: 'Income from fees',
|
||||||
|
controlName: 'incomeFromFeeAccountId',
|
||||||
|
options: this.incomeAccountOptions,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'incomeFromPenaltyAccountId',
|
||||||
|
label: 'Income From Penalties',
|
||||||
|
inputLabel: 'Income from penalties',
|
||||||
|
controlName: 'incomeFromPenaltyAccountId',
|
||||||
|
options: this.incomeAccountOptions,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'incomeFromInterestId',
|
||||||
|
label: 'Income From Interest',
|
||||||
|
inputLabel: 'Income from interest',
|
||||||
|
controlName: 'incomeFromInterestId',
|
||||||
|
options: this.incomeAccountOptions,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
otherMappingsControlName: 'incomeOtherGlMappings',
|
||||||
|
otherAccountOptions: this.incomeAccountOptions
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -65,7 +65,7 @@ $gray-900: #202124;
|
|||||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (width <= 600px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
&__select {
|
&__select {
|
||||||
|
|||||||
+57
-62
@@ -10,74 +10,69 @@
|
|||||||
<div class="spc-section">
|
<div class="spc-section">
|
||||||
<div class="spc-section__header">
|
<div class="spc-section__header">
|
||||||
<i class="fas fa-coins"></i>
|
<i class="fas fa-coins"></i>
|
||||||
<h3>{{ 'labels.inputs.Currency' | translate }}</h3>
|
<span>Currency & Account Status Configuration</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spc-grid">
|
<div class="spc-body">
|
||||||
<mat-form-field appearance="outline" class="spc-field">
|
<div class="spc-grid">
|
||||||
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="currencyCode"
|
|
||||||
matTooltip="{{ 'tooltips.The currency to be used' | translate }}"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
@for (currency of currencyData; track currency) {
|
|
||||||
<mat-option [value]="currency.code">
|
|
||||||
{{ currency.name }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="spc-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Track and report saving account' | translate }}"
|
|
||||||
formControlName="digitsAfterDecimal"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spc-section">
|
|
||||||
<div class="spc-section__header">
|
|
||||||
<i class="fas fa-calculator"></i>
|
|
||||||
<h3>{{ 'labels.inputs.Multiples' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="spc-grid">
|
|
||||||
<div class="spc-field--full spc-checkbox-row">
|
|
||||||
<mat-checkbox labelPosition="before" formControlName="setMultiples">
|
|
||||||
{{ 'labels.inputs.Set the saving installment in multiples of' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (savingProductCurrencyForm.value.setMultiples) {
|
|
||||||
<mat-form-field appearance="outline" class="spc-field">
|
<mat-form-field appearance="outline" class="spc-field">
|
||||||
<mat-label>{{ 'labels.inputs.Currency in multiples of' | translate }}</mat-label>
|
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
||||||
<input
|
<mat-select
|
||||||
type="number"
|
formControlName="currencyCode"
|
||||||
min="1"
|
matTooltip="{{ 'tooltips.The currency to be used' | translate }}"
|
||||||
matInput
|
required
|
||||||
matTooltip="{{ 'tooltips.Amount to be rounded off' | translate }}"
|
>
|
||||||
formControlName="inMultiplesOf"
|
@for (currency of currencyData; track currency) {
|
||||||
/>
|
<mat-option [value]="currency.code">
|
||||||
|
{{ currency.name }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-hint>Default: Indian Rupee - auto-set for domestic products</mat-hint>
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{ 'labels.inputs.Currency in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
|
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
}
|
|
||||||
|
<mat-form-field appearance="outline" class="spc-field">
|
||||||
|
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
||||||
|
<mat-select
|
||||||
|
formControlName="digitsAfterDecimal"
|
||||||
|
matTooltip="{{ 'tooltips.Track and report saving account' | translate }}"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
@for (places of decimalPlacesData; track places) {
|
||||||
|
<mat-option [value]="places">{{ places }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spc-alert">
|
||||||
|
<div class="spc-alert__content">
|
||||||
|
<h4>Dormancy Alert</h4>
|
||||||
|
<p>Enable automated notifications when an account becomes inactive based on product rules.</p>
|
||||||
|
</div>
|
||||||
|
<mat-radio-group class="spc-alert__choices" formControlName="dormancyAlert">
|
||||||
|
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||||
|
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||||
|
</mat-radio-group>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spc-alert">
|
||||||
|
<div class="spc-alert__content">
|
||||||
|
<h4>Nomination Facility</h4>
|
||||||
|
<p>Allow account holders to designate individuals to receive funds in the event of account holder's death.</p>
|
||||||
|
</div>
|
||||||
|
<mat-radio-group class="spc-alert__choices" formControlName="nominationFacility">
|
||||||
|
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||||
|
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||||
|
</mat-radio-group>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+51
-7
@@ -44,27 +44,71 @@ $gray-900: #202124;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spc-section__badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #eff6ff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.spc-grid {
|
.spc-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 4px 16px;
|
gap: 4px 16px;
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (width <= 600px) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spc-body {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.spc-field {
|
.spc-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
&--full {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.spc-checkbox-row {
|
.spc-alert {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px 0;
|
justify-content: space-between;
|
||||||
|
gap: 24px;
|
||||||
|
padding: 18px 24px 24px;
|
||||||
|
border-top: 1px solid $border;
|
||||||
|
background: #f8fafc;
|
||||||
|
|
||||||
|
@media (width <= 760px) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
h4 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #64748b;
|
||||||
|
max-width: 540px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__choices {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 18px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-45
@@ -6,11 +6,10 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, OnInit, Input, inject, DestroyRef } from '@angular/core';
|
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
||||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
@@ -21,19 +20,24 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
MatTooltip,
|
MatTooltip,
|
||||||
FaIconComponent,
|
MatRadioGroup,
|
||||||
MatCheckbox
|
MatRadioButton,
|
||||||
|
FaIconComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SavingProductCurrencyStepComponent implements OnInit {
|
export class SavingProductCurrencyStepComponent implements OnInit {
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
private destroyRef = inject(DestroyRef);
|
|
||||||
|
|
||||||
@Input() savingProductsTemplate: any;
|
@Input() savingProductsTemplate: any;
|
||||||
|
|
||||||
savingProductCurrencyForm: UntypedFormGroup;
|
savingProductCurrencyForm: UntypedFormGroup;
|
||||||
|
|
||||||
currencyData: any;
|
currencyData: any;
|
||||||
|
decimalPlacesData = [
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
3
|
||||||
|
];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.createSavingProductCurrencyForm();
|
this.createSavingProductCurrencyForm();
|
||||||
@@ -43,13 +47,11 @@ export class SavingProductCurrencyStepComponent implements OnInit {
|
|||||||
this.currencyData = this.savingProductsTemplate.currencyOptions;
|
this.currencyData = this.savingProductsTemplate.currencyOptions;
|
||||||
|
|
||||||
this.savingProductCurrencyForm.patchValue({
|
this.savingProductCurrencyForm.patchValue({
|
||||||
currencyCode: this.savingProductsTemplate.currency.code || this.currencyData[0].code,
|
currencyCode: this.savingProductsTemplate.currency?.code || this.currencyData?.[0]?.code || '',
|
||||||
digitsAfterDecimal: this.savingProductsTemplate.digitsAfterDecimal ?? '',
|
digitsAfterDecimal: this.savingProductsTemplate.digitsAfterDecimal ?? 2,
|
||||||
setMultiples: !!this.savingProductsTemplate.inMultiplesOf,
|
dormancyAlert: this.savingProductsTemplate.isDormancyTrackingActive ?? false,
|
||||||
inMultiplesOf: this.savingProductsTemplate.inMultiplesOf ?? ''
|
nominationFacility: this.savingProductsTemplate.nominationFacility ?? false
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setupConditionalValidation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createSavingProductCurrencyForm() {
|
createSavingProductCurrencyForm() {
|
||||||
@@ -59,39 +61,14 @@ export class SavingProductCurrencyStepComponent implements OnInit {
|
|||||||
Validators.required
|
Validators.required
|
||||||
],
|
],
|
||||||
digitsAfterDecimal: [
|
digitsAfterDecimal: [
|
||||||
'',
|
2,
|
||||||
[
|
[
|
||||||
Validators.required,
|
Validators.required,
|
||||||
Validators.min(0)
|
Validators.min(0)
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
setMultiples: [false],
|
dormancyAlert: [false],
|
||||||
inMultiplesOf: ['']
|
nominationFacility: [false]
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setupConditionalValidation() {
|
|
||||||
const inMultiplesOfControl = this.savingProductCurrencyForm.get('inMultiplesOf');
|
|
||||||
const setMultiplesControl = this.savingProductCurrencyForm.get('setMultiples');
|
|
||||||
if (setMultiplesControl?.value) {
|
|
||||||
inMultiplesOfControl?.setValidators([
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(1)
|
|
||||||
]);
|
|
||||||
inMultiplesOfControl?.updateValueAndValidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMultiplesControl?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((checked) => {
|
|
||||||
if (checked) {
|
|
||||||
inMultiplesOfControl?.setValidators([
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(1)
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
inMultiplesOfControl?.clearValidators();
|
|
||||||
inMultiplesOfControl?.setValue('');
|
|
||||||
}
|
|
||||||
inMultiplesOfControl?.updateValueAndValidity();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,13 +76,12 @@ export class SavingProductCurrencyStepComponent implements OnInit {
|
|||||||
const formValue = this.savingProductCurrencyForm.value;
|
const formValue = this.savingProductCurrencyForm.value;
|
||||||
const result: any = {
|
const result: any = {
|
||||||
currencyCode: formValue.currencyCode,
|
currencyCode: formValue.currencyCode,
|
||||||
digitsAfterDecimal: formValue.digitsAfterDecimal
|
digitsAfterDecimal: formValue.digitsAfterDecimal,
|
||||||
|
dormancyAlert: formValue.dormancyAlert,
|
||||||
|
isDormancyTrackingActive: formValue.dormancyAlert,
|
||||||
|
nominationFacility: formValue.nominationFacility
|
||||||
};
|
};
|
||||||
|
|
||||||
if (formValue.inMultiplesOf !== '' && formValue.inMultiplesOf !== null && formValue.inMultiplesOf !== undefined) {
|
|
||||||
result.inMultiplesOf = formValue.inMultiplesOf;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
@@ -54,4 +54,64 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="spd-section">
|
||||||
|
<div class="spd-section__header">
|
||||||
|
<i class="fas fa-tags"></i>
|
||||||
|
<h3>Product Classification</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spd-grid spd-grid--classification">
|
||||||
|
<mat-form-field appearance="outline" class="spd-field">
|
||||||
|
<mat-label>Account Type</mat-label>
|
||||||
|
<mat-select formControlName="accountType" required>
|
||||||
|
@for (accountType of accountTypeData; track accountType) {
|
||||||
|
<mat-option [value]="accountType">{{ accountType }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
<mat-error>Account Type <strong>is required</strong></mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spd-field">
|
||||||
|
<mat-label>Customer Segment</mat-label>
|
||||||
|
<mat-select formControlName="customerSegment">
|
||||||
|
@for (customerSegment of customerSegmentData; track customerSegment) {
|
||||||
|
<mat-option [value]="customerSegment">{{ customerSegment }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spd-field">
|
||||||
|
<mat-label>Product Code</mat-label>
|
||||||
|
<input matInput formControlName="productCode" />
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spd-field" (click)="effectiveToPicker.open()">
|
||||||
|
<mat-label>Effective To</mat-label>
|
||||||
|
<input matInput [matDatepicker]="effectiveToPicker" formControlName="effectiveTo" />
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="effectiveToPicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #effectiveToPicker></mat-datepicker>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spd-field" (click)="endDatePicker.open()">
|
||||||
|
<mat-label>End Date</mat-label>
|
||||||
|
<input matInput [matDatepicker]="endDatePicker" formControlName="endDate" />
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #endDatePicker></mat-datepicker>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spd-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="spd-field spd-field--full spd-checkbox-row">
|
||||||
|
<mat-checkbox formControlName="shareLinkingEnabled">Share Linking Enabled</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+11
-1
@@ -46,11 +46,15 @@ $gray-900: #202124;
|
|||||||
gap: 4px 16px;
|
gap: 4px 16px;
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (width <= 600px) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spd-grid--classification {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.spd-field {
|
.spd-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -58,3 +62,9 @@ $gray-900: #202124;
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spd-checkbox-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 56px;
|
||||||
|
}
|
||||||
|
|||||||
+130
-9
@@ -6,11 +6,13 @@
|
|||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* 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, ReactiveFormsModule } from '@angular/forms';
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -24,12 +26,29 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
FaIconComponent
|
FaIconComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SavingProductDetailsStepComponent implements OnInit {
|
export class SavingProductDetailsStepComponent implements OnInit, OnChanges {
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
@Input() savingProductsTemplate: any;
|
@Input() savingProductsTemplate: any;
|
||||||
|
|
||||||
savingProductDetailsForm: UntypedFormGroup;
|
savingProductDetailsForm: UntypedFormGroup;
|
||||||
|
accountTypeData: string[] = [];
|
||||||
|
customerSegmentData: string[] = [];
|
||||||
|
yesNoOptions: Array<{
|
||||||
|
value: boolean;
|
||||||
|
label: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
label: 'Yes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
label: 'No'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.createSavingProductDetailsForm();
|
this.createSavingProductDetailsForm();
|
||||||
@@ -37,11 +56,13 @@ export class SavingProductDetailsStepComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.savingProductsTemplate) {
|
if (this.savingProductsTemplate) {
|
||||||
this.savingProductDetailsForm.patchValue({
|
this.applyTemplate(this.savingProductsTemplate);
|
||||||
name: this.savingProductsTemplate.name,
|
}
|
||||||
shortName: this.savingProductsTemplate.shortName,
|
}
|
||||||
description: this.savingProductsTemplate.description
|
|
||||||
});
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes.savingProductsTemplate?.currentValue) {
|
||||||
|
this.applyTemplate(changes.savingProductsTemplate.currentValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,11 +76,111 @@ export class SavingProductDetailsStepComponent implements OnInit {
|
|||||||
'',
|
'',
|
||||||
Validators.required
|
Validators.required
|
||||||
],
|
],
|
||||||
description: ['']
|
description: [''],
|
||||||
|
accountType: [
|
||||||
|
null,
|
||||||
|
Validators.required
|
||||||
|
],
|
||||||
|
customerSegment: [null],
|
||||||
|
productCode: ['SB-IND-001'],
|
||||||
|
effectiveDate: [''],
|
||||||
|
effectiveTo: [''],
|
||||||
|
endDate: [''],
|
||||||
|
introducerRequired: [false],
|
||||||
|
shareLinkingEnabled: [false],
|
||||||
|
nominationFacility: [false],
|
||||||
|
dormancyAlert: [false]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getOptionNames(options: any[]): string[] {
|
||||||
|
if (!Array.isArray(options)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
|
.map((option) => option?.name ?? option?.value ?? option)
|
||||||
|
.filter((value) => typeof value === 'string' && value.trim().length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private applyTemplate(template: any) {
|
||||||
|
const accountTypeOptions = this.getOptionNames(template.accountTypeOptions);
|
||||||
|
const customerSegmentOptions = this.getOptionNames(template.customerSegmentOptions);
|
||||||
|
const yesNoOptions = this.getYesNoOptions(template.yesNoOptions);
|
||||||
|
|
||||||
|
this.accountTypeData = accountTypeOptions;
|
||||||
|
this.customerSegmentData = customerSegmentOptions;
|
||||||
|
if (yesNoOptions.length) {
|
||||||
|
this.yesNoOptions = yesNoOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use existing product data if available (when editing), otherwise use template defaults
|
||||||
|
const isEditing = !!template.id;
|
||||||
|
this.savingProductDetailsForm.patchValue({
|
||||||
|
name: isEditing ? template.name : template.name || '',
|
||||||
|
shortName: isEditing ? template.shortName : template.shortName || '',
|
||||||
|
description: template.description,
|
||||||
|
accountType: template.accountType ?? this.accountTypeData[0] ?? null,
|
||||||
|
customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null,
|
||||||
|
productCode: template.productCode ?? 'SB-IND-001',
|
||||||
|
effectiveDate: template.effectiveDate ? new Date(template.effectiveDate) : '',
|
||||||
|
effectiveTo: template.effectiveTo ? new Date(template.effectiveTo) : '',
|
||||||
|
endDate: template.endDate ? new Date(template.endDate) : '',
|
||||||
|
introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes',
|
||||||
|
shareLinkingEnabled: !!template.shareLinkingEnabled,
|
||||||
|
nominationFacility: !!template.nominationFacility,
|
||||||
|
dormancyAlert: !!template.dormancyAlert
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getYesNoOptions(options: any[]): Array<{ value: boolean; label: string }> {
|
||||||
|
if (!Array.isArray(options)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return options
|
||||||
|
.map((option) => {
|
||||||
|
const rawValue = option?.name ?? option?.value ?? option;
|
||||||
|
const normalized = typeof rawValue === 'string' ? rawValue.trim().toUpperCase() : rawValue;
|
||||||
|
|
||||||
|
if (normalized === 'YES') {
|
||||||
|
return {
|
||||||
|
value: true,
|
||||||
|
label: 'Yes'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === 'NO') {
|
||||||
|
return {
|
||||||
|
value: false,
|
||||||
|
label: 'No'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((value) => !!value);
|
||||||
|
}
|
||||||
|
|
||||||
get savingProductDetails() {
|
get savingProductDetails() {
|
||||||
return this.savingProductDetailsForm.value;
|
const formValue = { ...this.savingProductDetailsForm.value };
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevEffectiveDate: Date = this.savingProductDetailsForm.value.effectiveDate;
|
||||||
|
const prevEffectiveTo: Date = this.savingProductDetailsForm.value.effectiveTo;
|
||||||
|
const prevEndDate: Date = this.savingProductDetailsForm.value.endDate;
|
||||||
|
|
||||||
|
if (formValue.effectiveDate instanceof Date) {
|
||||||
|
formValue.effectiveDate = this.dateUtils.formatDate(prevEffectiveDate, dateFormat) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formValue.effectiveTo instanceof Date) {
|
||||||
|
formValue.effectiveTo = this.dateUtils.formatDate(prevEffectiveTo, dateFormat) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formValue.endDate instanceof Date) {
|
||||||
|
formValue.endDate = this.dateUtils.formatDate(prevEndDate, dateFormat) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return formValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+351
@@ -0,0 +1,351 @@
|
|||||||
|
<!--
|
||||||
|
Copyright since 2025 Mifos Initiative
|
||||||
|
|
||||||
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
-->
|
||||||
|
<div class="spl-section">
|
||||||
|
<div class="spl-section__header">
|
||||||
|
<span class="spl-section__icon">
|
||||||
|
<i class="fas fa-chart-bar"></i>
|
||||||
|
</span>
|
||||||
|
<div class="spl-section__title">Transaction & Product Limits</div>
|
||||||
|
<div class="spl-section__badge">R&D BASED DEFAULTS</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spl-section__body">
|
||||||
|
<div class="spl-features">
|
||||||
|
<div class="spl-features__header">Account Features (as per RBI guidelines)</div>
|
||||||
|
|
||||||
|
<div class="spl-features__grid">
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-automobile"></i>
|
||||||
|
ATM Facility
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.atmFacility" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-wallet"></i>
|
||||||
|
ATM Withdrawal
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
class="spl-select"
|
||||||
|
[(ngModel)]="accountFacilityConfig.atmWithdrawalFacility"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
>
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-mobile-alt"></i>
|
||||||
|
UPI Facility
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.upiFacility" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-exchange-alt"></i>
|
||||||
|
POS Transactions
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
class="spl-select"
|
||||||
|
[(ngModel)]="accountFacilityConfig.posTransactionsFacility"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
>
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-book"></i>
|
||||||
|
Cheque Book
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.chequeBook" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-bolt"></i>
|
||||||
|
IMPS Facility
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.impsFacility" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-sms"></i>
|
||||||
|
SMS Alerts
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.smsAlerts" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-bell"></i>
|
||||||
|
BBPS Facility
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.bbpsFacility" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-shield-alt"></i>
|
||||||
|
Insurance Cover
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.insuranceCover" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-bolt"></i>
|
||||||
|
BEPG Facility
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.bepgFacility" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-money-check-alt"></i>
|
||||||
|
NEFT Facility
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.neftFacility" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-money-check"></i>
|
||||||
|
RTGS Facility
|
||||||
|
</span>
|
||||||
|
<select class="spl-select" [(ngModel)]="accountFacilityConfig.rtgsFacility" (ngModelChange)="markAsDirty()">
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-balance-scale"></i>
|
||||||
|
Inter-Bank Total Cap
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
class="spl-select"
|
||||||
|
[(ngModel)]="accountFacilityConfig.interBankTotalCapFacility"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
>
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-arrow-right-arrow-left"></i>
|
||||||
|
Withdrawal Facility
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
class="spl-select"
|
||||||
|
[(ngModel)]="accountFacilityConfig.withdrawalFacility"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
>
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-id-card"></i>
|
||||||
|
Deposit Facility (KYC)
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
class="spl-select"
|
||||||
|
[(ngModel)]="accountFacilityConfig.depositFacilityKyc"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
>
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="spl-feature">
|
||||||
|
<span class="spl-feature__label">
|
||||||
|
<i class="fas fa-credit-card"></i>
|
||||||
|
Virtual Debit Card
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
class="spl-select"
|
||||||
|
[(ngModel)]="accountFacilityConfig.virtualDebitCard"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
>
|
||||||
|
<option [ngValue]="false">No</option>
|
||||||
|
<option [ngValue]="true">Yes</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spl-table-wrap">
|
||||||
|
<table class="spl-table">
|
||||||
|
<colgroup>
|
||||||
|
<col class="spl-table__col--label" />
|
||||||
|
<col class="spl-table__col--value" />
|
||||||
|
<col class="spl-table__col--value" />
|
||||||
|
<col class="spl-table__col--value" />
|
||||||
|
</colgroup>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Limit Type</th>
|
||||||
|
<th>General Limit (Daily)</th>
|
||||||
|
<th>Minor Account Limit</th>
|
||||||
|
<th>Small Saving Limit</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (row of accountFacilityLimits; track row.limitType) {
|
||||||
|
<tr>
|
||||||
|
<td>{{ row.limitType }}</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" class="spl-input" [(ngModel)]="row.generalLimit" (ngModelChange)="markAsDirty()" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="spl-input"
|
||||||
|
[(ngModel)]="row.minorAccountLimit"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
[disabled]="row.limitType === 'RTGS Limit (Min 2L)'"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="spl-input"
|
||||||
|
[(ngModel)]="row.smallSavingLimit"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
[disabled]="row.limitType === 'RTGS Limit (Min 2L)'"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spl-table-wrap spl-table-wrap--kyc">
|
||||||
|
<h4 class="mat-h4">Deposit Limit (KYC)</h4>
|
||||||
|
<table class="spl-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Account Type</th>
|
||||||
|
<th>KYC Type</th>
|
||||||
|
<th>Limit</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>General</td>
|
||||||
|
<td>
|
||||||
|
<select class="spl-select" [(ngModel)]="depositLimitKyc.general.kycType" (ngModelChange)="markAsDirty()">
|
||||||
|
<option>Full KYC</option>
|
||||||
|
<option>Min KYC</option>
|
||||||
|
<option>No KYC</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="spl-input"
|
||||||
|
[(ngModel)]="depositLimitKyc.general.limit"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Minor</td>
|
||||||
|
<td>
|
||||||
|
<select class="spl-select" [(ngModel)]="depositLimitKyc.minor.kycType" (ngModelChange)="markAsDirty()">
|
||||||
|
<option>Guardian KYC</option>
|
||||||
|
<option>Full KYC</option>
|
||||||
|
<option>Min KYC</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="spl-input"
|
||||||
|
[(ngModel)]="depositLimitKyc.minor.limit"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Small Saving</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
class="spl-select"
|
||||||
|
[(ngModel)]="depositLimitKyc.smallSaving.kycType"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
>
|
||||||
|
<option>Min KYC</option>
|
||||||
|
<option>Full KYC</option>
|
||||||
|
<option>Guardian KYC</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
class="spl-input"
|
||||||
|
[(ngModel)]="depositLimitKyc.smallSaving.limit"
|
||||||
|
(ngModelChange)="markAsDirty()"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spl-note">
|
||||||
|
<strong>Note:</strong>
|
||||||
|
Small Saving Account limits are strictly capped at Rs.1,00,000 maximum balance and Rs.10,000 monthly withdrawal
|
||||||
|
per RBI Master Circular. Minor accounts are restricted from RTGS and high-risk digital products (BEPG/BBPS) by
|
||||||
|
default.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
+257
@@ -0,0 +1,257 @@
|
|||||||
|
/**
|
||||||
|
* Copyright since 2025 Mifos Initiative
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
$border: #d9e2ec;
|
||||||
|
$border-strong: #c7d2de;
|
||||||
|
$text: #24364b;
|
||||||
|
$muted: #64748b;
|
||||||
|
$header-bg: #f8fafc;
|
||||||
|
$highlight-bg: #eff6ff;
|
||||||
|
|
||||||
|
.spl-section {
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-section__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
background: $header-bg;
|
||||||
|
border-bottom: 1px solid $border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-section__icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 14px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-section__title {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-section__badge {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-section__body {
|
||||||
|
padding: 18px 20px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-subsection {
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
& + & {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-subsection__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: $header-bg;
|
||||||
|
border-bottom: 1px solid $border;
|
||||||
|
color: $text;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-subsection__icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #eff6ff;
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 13px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-subsection__body {
|
||||||
|
padding: 16px 16px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-table {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
table-layout: fixed;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 1px solid $border;
|
||||||
|
|
||||||
|
col.spl-table__col--label {
|
||||||
|
width: 22%;
|
||||||
|
}
|
||||||
|
|
||||||
|
col.spl-table__col--value {
|
||||||
|
width: 26%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid $border;
|
||||||
|
padding: 6px 8px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #64748b;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child {
|
||||||
|
background: #f8fafc;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $text;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
font-size: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-table__highlight td:first-child {
|
||||||
|
color: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-input,
|
||||||
|
.spl-select {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-input {
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid $border-strong;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0 8px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-input:disabled,
|
||||||
|
.spl-select:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: $muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-select {
|
||||||
|
height: 30px;
|
||||||
|
border: 1px solid $border-strong;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0 6px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-input--stacked {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-input--disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: $muted;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-table__muted {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #94a3b8;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-note {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid $border;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: $muted;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-features {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-features__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 12px 16px;
|
||||||
|
|
||||||
|
@media (width <= 900px) {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 640px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-feature {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spl-feature__label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #334155;
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: #2563eb;
|
||||||
|
font-size: 12px;
|
||||||
|
width: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
+250
@@ -0,0 +1,250 @@
|
|||||||
|
/**
|
||||||
|
* Copyright since 2025 Mifos Initiative
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
|
type BooleanFeatureKey =
|
||||||
|
| 'atmFacility'
|
||||||
|
| 'atmWithdrawalFacility'
|
||||||
|
| 'upiFacility'
|
||||||
|
| 'posTransactionsFacility'
|
||||||
|
| 'chequeBook'
|
||||||
|
| 'impsFacility'
|
||||||
|
| 'smsAlerts'
|
||||||
|
| 'bbpsFacility'
|
||||||
|
| 'insuranceCover'
|
||||||
|
| 'bepgFacility'
|
||||||
|
| 'neftFacility'
|
||||||
|
| 'rtgsFacility'
|
||||||
|
| 'interBankTotalCapFacility'
|
||||||
|
| 'withdrawalFacility'
|
||||||
|
| 'depositFacilityKyc'
|
||||||
|
| 'virtualDebitCard';
|
||||||
|
|
||||||
|
interface FacilityLimitRow {
|
||||||
|
limitType: string;
|
||||||
|
generalLimit: string | number | null;
|
||||||
|
minorAccountLimit: string | number | null;
|
||||||
|
smallSavingLimit: string | number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'mifosx-saving-product-limits-step',
|
||||||
|
templateUrl: './saving-product-limits-step.component.html',
|
||||||
|
styleUrls: ['./saving-product-limits-step.component.scss'],
|
||||||
|
imports: [
|
||||||
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
|
FormsModule
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class SavingProductLimitsStepComponent implements OnChanges {
|
||||||
|
@Input() savingProductsTemplate: any;
|
||||||
|
pristine = true;
|
||||||
|
|
||||||
|
yesNoOptions: Array<{
|
||||||
|
value: boolean;
|
||||||
|
label: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
label: 'Yes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
label: 'No'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
accountFacilityConfig: Record<BooleanFeatureKey, boolean> = {
|
||||||
|
atmFacility: true,
|
||||||
|
atmWithdrawalFacility: true,
|
||||||
|
upiFacility: true,
|
||||||
|
posTransactionsFacility: true,
|
||||||
|
chequeBook: true,
|
||||||
|
impsFacility: true,
|
||||||
|
smsAlerts: true,
|
||||||
|
bbpsFacility: true,
|
||||||
|
insuranceCover: false,
|
||||||
|
bepgFacility: true,
|
||||||
|
neftFacility: true,
|
||||||
|
rtgsFacility: true,
|
||||||
|
interBankTotalCapFacility: true,
|
||||||
|
withdrawalFacility: true,
|
||||||
|
depositFacilityKyc: true,
|
||||||
|
virtualDebitCard: false
|
||||||
|
};
|
||||||
|
|
||||||
|
accountFacilityLimits: FacilityLimitRow[] = [
|
||||||
|
{
|
||||||
|
limitType: 'ATM Withdrawal',
|
||||||
|
generalLimit: 40000,
|
||||||
|
minorAccountLimit: 5000,
|
||||||
|
smallSavingLimit: 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'POS Transactions',
|
||||||
|
generalLimit: 100000,
|
||||||
|
minorAccountLimit: 2000,
|
||||||
|
smallSavingLimit: 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'UPI Limit',
|
||||||
|
generalLimit: 50000,
|
||||||
|
minorAccountLimit: 1000,
|
||||||
|
smallSavingLimit: 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'IMPS Limit',
|
||||||
|
generalLimit: 50000,
|
||||||
|
minorAccountLimit: 100,
|
||||||
|
smallSavingLimit: 5000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'BBPS',
|
||||||
|
generalLimit: 25000,
|
||||||
|
minorAccountLimit: 0,
|
||||||
|
smallSavingLimit: 2000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'BEPG',
|
||||||
|
generalLimit: 25000,
|
||||||
|
minorAccountLimit: 0,
|
||||||
|
smallSavingLimit: 2000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'NEFT Limit',
|
||||||
|
generalLimit: 1000000,
|
||||||
|
minorAccountLimit: 25000,
|
||||||
|
smallSavingLimit: 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'RTGS Limit (Min 2L)',
|
||||||
|
generalLimit: 5000000,
|
||||||
|
minorAccountLimit: 'N/A',
|
||||||
|
smallSavingLimit: 'N/A'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'Inter-Bank Total Cap',
|
||||||
|
generalLimit: 5000000,
|
||||||
|
minorAccountLimit: 25000,
|
||||||
|
smallSavingLimit: 10000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
limitType: 'Withdrawal Limit',
|
||||||
|
generalLimit: 'UNLIMITED',
|
||||||
|
minorAccountLimit: 10000,
|
||||||
|
smallSavingLimit: 10000
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
depositLimitKyc = {
|
||||||
|
general: {
|
||||||
|
kycType: 'Full KYC',
|
||||||
|
limit: 'No Limit'
|
||||||
|
},
|
||||||
|
minor: {
|
||||||
|
kycType: 'Guardian KYC',
|
||||||
|
limit: 100000
|
||||||
|
},
|
||||||
|
smallSaving: {
|
||||||
|
kycType: 'Min KYC',
|
||||||
|
limit: 50000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (changes.savingProductsTemplate?.currentValue) {
|
||||||
|
const template = changes.savingProductsTemplate.currentValue;
|
||||||
|
this.setYesNoOptions(template);
|
||||||
|
this.populateFromTemplate(template);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsDirty() {
|
||||||
|
this.pristine = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateFromTemplate(template: any) {
|
||||||
|
const isEditing = !!template.id;
|
||||||
|
|
||||||
|
// Populate accountFacilityConfig from existing product
|
||||||
|
if (isEditing && template.accountFacilityConfig) {
|
||||||
|
this.accountFacilityConfig = { ...this.accountFacilityConfig, ...template.accountFacilityConfig };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate accountFacilityLimits from existing product
|
||||||
|
if (isEditing && template.accountFacilityLimits && template.accountFacilityLimits.length > 0) {
|
||||||
|
this.accountFacilityLimits = template.accountFacilityLimits.map((limit: any) => ({
|
||||||
|
limitType: limit.limitType || '',
|
||||||
|
generalLimit: limit.generalLimit ?? null,
|
||||||
|
minorAccountLimit: limit.minorAccountLimit ?? null,
|
||||||
|
smallSavingLimit: limit.smallSavingLimit ?? null
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate depositLimitKyc from existing product
|
||||||
|
if (isEditing && template.depositLimitKyc) {
|
||||||
|
this.depositLimitKyc = {
|
||||||
|
general: {
|
||||||
|
kycType: template.depositLimitKyc.general?.kycType || 'Full KYC',
|
||||||
|
limit: template.depositLimitKyc.general?.limit || 'No Limit'
|
||||||
|
},
|
||||||
|
minor: {
|
||||||
|
kycType: template.depositLimitKyc.minor?.kycType || 'Guardian KYC',
|
||||||
|
limit: template.depositLimitKyc.minor?.limit ?? 100000
|
||||||
|
},
|
||||||
|
smallSaving: {
|
||||||
|
kycType: template.depositLimitKyc.smallSaving?.kycType || 'Min KYC',
|
||||||
|
limit: template.depositLimitKyc.smallSaving?.limit ?? 50000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setYesNoOptions(template: any) {
|
||||||
|
const options = Array.isArray(template?.yesNoOptions) ? template.yesNoOptions : [];
|
||||||
|
const mappedOptions = options
|
||||||
|
.map((option: any) => {
|
||||||
|
const rawValue = option?.name ?? option?.value ?? option;
|
||||||
|
const normalized = typeof rawValue === 'string' ? rawValue.trim().toUpperCase() : rawValue;
|
||||||
|
|
||||||
|
if (normalized === 'YES') {
|
||||||
|
return {
|
||||||
|
value: true,
|
||||||
|
label: 'Yes'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === 'NO') {
|
||||||
|
return {
|
||||||
|
value: false,
|
||||||
|
label: 'No'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((value: any) => !!value);
|
||||||
|
|
||||||
|
if (mappedOptions.length) {
|
||||||
|
this.yesNoOptions = mappedOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get savingProductLimits() {
|
||||||
|
return {
|
||||||
|
accountFacilityConfig: { ...this.accountFacilityConfig },
|
||||||
|
accountFacilityLimits: this.accountFacilityLimits.map((row) => ({ ...row })),
|
||||||
|
depositLimitKyc: {
|
||||||
|
...this.depositLimitKyc
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
+495
-17
@@ -25,25 +25,107 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (
|
||||||
|
savingProduct.accountType ||
|
||||||
|
savingProduct.customerSegment ||
|
||||||
|
savingProduct.productCode ||
|
||||||
|
savingProduct.effectiveTo ||
|
||||||
|
savingProduct.introducerRequired !== undefined ||
|
||||||
|
savingProduct.shareLinkingEnabled !== undefined
|
||||||
|
) {
|
||||||
|
<h3 class="mat-h3 flex-100">Product Classification</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
|
||||||
|
@if (savingProduct.accountType) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Account Type:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.accountType }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.customerSegment) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Customer Segment:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.customerSegment }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.productCode) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Product Code:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.productCode }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.effectiveTo) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Effective To:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.effectiveTo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.endDate) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">End Date:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.endDate }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.introducerRequired !== undefined) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Introducer Required:</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
savingProduct.introducerRequired === true || savingProduct.introducerRequired === 'Yes' ? 'Yes' : 'No'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.shareLinkingEnabled !== undefined) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Share Linking Enabled:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.shareLinkingEnabled | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Currency' | translate }}</h3>
|
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||||
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
<div class="flex-100 layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}:</span>
|
||||||
<span class="flex-60">{{
|
<span class="flex-60">{{
|
||||||
savingProduct.currencyCode | find: savingProductsTemplate.currencyOptions : 'code' : 'name'
|
savingProduct.currency?.name ||
|
||||||
|
savingProduct.currency?.displayLabel ||
|
||||||
|
(savingProduct.currencyCode | find: savingProductsTemplate?.currencyOptions : 'code' : 'name') ||
|
||||||
|
savingProduct.currencyCode ||
|
||||||
|
'-'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}:</span>
|
||||||
<span class="flex-60">{{ savingProduct.digitsAfterDecimal ?? savingProduct.currency?.decimalPlaces }}</span>
|
<span class="flex-60">{{ savingProduct.digitsAfterDecimal ?? savingProduct.currency?.decimalPlaces ?? 2 }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (savingProduct.inMultiplesOf) {
|
@if (savingProduct.currency?.inMultiplesOf !== undefined && savingProduct.currency?.inMultiplesOf !== null) {
|
||||||
<div class="flex-100 layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40">{{ 'labels.inputs.Currency in multiples of' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Currency in multiples of' | translate }}:</span>
|
||||||
<span class="flex-60">{{ savingProduct.inMultiplesOf }}</span>
|
<span class="flex-60">{{ savingProduct.currency.inMultiplesOf }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.dormancyAlert !== undefined) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Dormancy Alert:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.dormancyAlert | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.nominationFacility !== undefined) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Nomination Facility:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.nominationFacility | yesNo }}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +141,7 @@
|
|||||||
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
||||||
<span class="flex-60">{{
|
<span class="flex-60">{{
|
||||||
savingProduct.interestCompoundingPeriodType
|
savingProduct.interestCompoundingPeriodType
|
||||||
| find: savingProductsTemplate.interestCompoundingPeriodTypeOptions : 'id' : 'value'
|
| find: savingProductsTemplate?.interestCompoundingPeriodTypeOptions : 'id' : 'value'
|
||||||
| translateKey: 'catalogs'
|
| translateKey: 'catalogs'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,16 +150,16 @@
|
|||||||
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
||||||
<span class="flex-60">{{
|
<span class="flex-60">{{
|
||||||
savingProduct.interestPostingPeriodType
|
savingProduct.interestPostingPeriodType
|
||||||
| find: savingProductsTemplate.interestPostingPeriodTypeOptions : 'id' : 'value'
|
| find: savingProductsTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value'
|
||||||
| translateKey: 'catalogs'
|
| translateKey: 'catalogs'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-100 layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Calculated using' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Interest Calculation Type' | translate }}:</span>
|
||||||
<span class="flex-60">{{
|
<span class="flex-60">{{
|
||||||
savingProduct.interestCalculationType
|
savingProduct.interestCalculationType
|
||||||
| find: savingProductsTemplate.interestCalculationTypeOptions : 'id' : 'value'
|
| find: interestCalculationTypeOptionData : 'id' : 'value'
|
||||||
| translateKey: 'catalogs'
|
| translateKey: 'catalogs'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,11 +168,56 @@
|
|||||||
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
||||||
<span class="flex-60">{{
|
<span class="flex-60">{{
|
||||||
savingProduct.interestCalculationDaysInYearType
|
savingProduct.interestCalculationDaysInYearType
|
||||||
| find: savingProductsTemplate.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
|
| find: savingProductsTemplate?.interestCalculationDaysInYearTypeOptions : 'id' : 'value'
|
||||||
| translateKey: 'catalogs'
|
| translateKey: 'catalogs'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.effectiveDate) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Effective Date:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.effectiveDate }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Interest Calculate:</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
savingProduct.interestCalculateSelector
|
||||||
|
| find: savingProductsTemplate?.interestCalculationTypeOptions : 'id' : 'value'
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.averagePeriod) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Average Period:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.averagePeriod }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Auto Interest Post:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.autoPost | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (
|
||||||
|
savingProduct.frequencyFixDay !== undefined &&
|
||||||
|
savingProduct.frequencyFixDay !== null &&
|
||||||
|
savingProduct.frequencyFixDay !== ''
|
||||||
|
) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Frequency (Fix Day):</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.frequencyFixDay }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.lastInterestDate) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Last Interest Date:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.lastInterestDate }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Settings' | translate }}</h3>
|
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Settings' | translate }}</h3>
|
||||||
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
@@ -108,7 +235,8 @@
|
|||||||
savingProduct.lockinPeriodFrequency +
|
savingProduct.lockinPeriodFrequency +
|
||||||
' ' +
|
' ' +
|
||||||
(savingProduct.lockinPeriodFrequencyType
|
(savingProduct.lockinPeriodFrequencyType
|
||||||
| find: savingProductsTemplate.lockinPeriodFrequencyTypeOptions : 'id' : 'value') | translateKey: 'catalogs'
|
| find: savingProductsTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value')
|
||||||
|
| translateKey: 'catalogs'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -118,6 +246,28 @@
|
|||||||
<span class="flex-60">{{ savingProduct.withdrawalFeeForTransfers | yesNo }}</span>
|
<span class="flex-60">{{ savingProduct.withdrawalFeeForTransfers | yesNo }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.balanceFrequency) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Balance Frequency:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.balanceFrequency }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Enable Lock-in Period:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.enableLockinPeriod | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.enableLockinPeriod && savingProduct.lockinPeriodFrequency) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Lock-in Period:</span>
|
||||||
|
<span class="flex-60">
|
||||||
|
{{ savingProduct.lockinPeriodFrequency }}
|
||||||
|
{{ getLockinPeriodFrequencyTypeLabel(savingProduct.lockinPeriodFrequencyType) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (savingProduct.minBalanceForInterestCalculation) {
|
@if (savingProduct.minBalanceForInterestCalculation) {
|
||||||
<div class="flex-100 layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40">{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}:</span>
|
||||||
@@ -146,7 +296,7 @@
|
|||||||
<div class="flex-100 layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40">{{ 'labels.inputs.Tax Group' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Tax Group' | translate }}:</span>
|
||||||
<span class="flex-60">{{
|
<span class="flex-60">{{
|
||||||
savingProduct.taxGroupId | find: savingProductsTemplate.taxGroupOptions : 'id' : 'name'
|
savingProduct.taxGroupId | find: savingProductsTemplate?.taxGroupOptions : 'id' : 'name'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -156,6 +306,11 @@
|
|||||||
<span class="flex-60">{{ savingProduct.allowOverdraft | yesNo }}</span>
|
<span class="flex-60">{{ savingProduct.allowOverdraft | yesNo }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Account Closed with Zero Balance in Day Closing:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.accountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if (savingProduct.minOverdraftForInterestCalculation) {
|
@if (savingProduct.minOverdraftForInterestCalculation) {
|
||||||
<div class="flex-100 layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40"
|
<span class="flex-40"
|
||||||
@@ -195,6 +350,77 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<h3 class="mat-h3 flex-100">Account Facilities</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
@if (savingProduct.accountFacilityConfig) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
|
@for (feature of accountFacilityFeatureItems; track feature.key) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ feature.label }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.accountFacilityConfig[feature.key] | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.accountFacilityLimits?.length) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
|
<h4 class="mat-h4 flex-100">Account Facility Limits</h4>
|
||||||
|
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="savingProduct.accountFacilityLimits">
|
||||||
|
<ng-container matColumnDef="limitType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Limit Type</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.limitType }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="generalLimit">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>General Limit</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.generalLimit }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="minorAccountLimit">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Minor Account Limit</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.minorAccountLimit }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="smallSavingLimit">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Small Saving Limit</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.smallSavingLimit }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr
|
||||||
|
mat-header-row
|
||||||
|
*matHeaderRowDef="['limitType', 'generalLimit', 'minorAccountLimit', 'smallSavingLimit']"
|
||||||
|
></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: ['limitType', 'generalLimit', 'minorAccountLimit', 'smallSavingLimit']"
|
||||||
|
></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.depositLimitKyc) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
|
<h4 class="mat-h4 flex-100">Deposit Limit (KYC)</h4>
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">General:</span>
|
||||||
|
<span class="flex-60"
|
||||||
|
>{{ savingProduct.depositLimitKyc.general.kycType }} - {{ savingProduct.depositLimitKyc.general.limit }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Minor:</span>
|
||||||
|
<span class="flex-60"
|
||||||
|
>{{ savingProduct.depositLimitKyc.minor.kycType }} - {{ savingProduct.depositLimitKyc.minor.limit }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Small Saving:</span>
|
||||||
|
<span class="flex-60">
|
||||||
|
{{ savingProduct.depositLimitKyc.smallSaving.kycType }} -
|
||||||
|
{{ savingProduct.depositLimitKyc.smallSaving.limit }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (savingProduct.charges.length) {
|
@if (savingProduct.charges.length) {
|
||||||
<div class="layout-row-wrap responsive-column flex-100">
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Charges' | translate }}</h3>
|
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Charges' | translate }}</h3>
|
||||||
@@ -233,9 +459,262 @@
|
|||||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Accounting' | translate }}</h3>
|
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Accounting' | translate }}</h3>
|
||||||
|
|
||||||
<mat-divider class="flex-100"></mat-divider>
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
@if (isCashOrAccrualAccounting()) {
|
||||||
|
<div class="spa-gl-summary flex-100">
|
||||||
|
<div class="spa-gl-summary__header">
|
||||||
|
<h4 class="mat-h4">GL Mapping</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mifosx-view-savings-accounting-details [accountingRule]="accountingRule" [accountingMappings]="accountingMappings">
|
<div class="spa-gl-section">
|
||||||
</mifosx-view-savings-accounting-details>
|
<h5 class="mat-h5">Assets</h5>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Savings reference account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.savingsReferenceAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Overdraft portfolio control</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.overdraftPortfolioControlId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Interest receivable account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.interestReceivableAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Fees receivable account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.feesReceivableAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Penalties receivable account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.penaltiesReceivableAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.assetAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spa-gl-section">
|
||||||
|
<h5 class="mat-h5">Liabilities</h5>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Savings control account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.savingsControlAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Transfers in suspense account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.transfersInSuspenseAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Interest payable account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.interestPayableAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
@if (savingProduct.isDormancyTrackingActive) {
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Escheat liability account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.escheatLiabilityId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spa-gl-section">
|
||||||
|
<h5 class="mat-h5">Expenses</h5>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Interest on savings account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.interestOnSavingsAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.expenseAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Write off account</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.writeOffAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.expenseAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spa-gl-section">
|
||||||
|
<h5 class="mat-h5">Income</h5>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Income from fees</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.incomeFromFeeAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Income from penalties</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.incomeFromPenaltyAccountId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
<div class="spa-gl-row">
|
||||||
|
<span class="spa-gl-row__label">Income from interest</span>
|
||||||
|
<span class="spa-gl-row__value">{{
|
||||||
|
formatGlAccountById(
|
||||||
|
savingProduct.incomeFromInterestId,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (savingProduct.otherGlMappings) {
|
||||||
|
<div class="spa-other-gl-summary flex-100">
|
||||||
|
<div class="spa-gl-summary__header">
|
||||||
|
<h4 class="mat-h4">Other GL Mappings</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.otherGlMappings.assets?.length) {
|
||||||
|
<table
|
||||||
|
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
||||||
|
mat-table
|
||||||
|
[dataSource]="savingProduct.otherGlMappings.assets"
|
||||||
|
>
|
||||||
|
<ng-container matColumnDef="accountName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Asset Account</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="glCode">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
{{
|
||||||
|
formatGlAccountByCode(row.glCode, savingProductsTemplate?.accountingMappingOptions.assetAccountOptions)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="['accountName', 'glCode']"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glCode']"></tr>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.otherGlMappings.liabilities?.length) {
|
||||||
|
<table
|
||||||
|
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
||||||
|
mat-table
|
||||||
|
[dataSource]="savingProduct.otherGlMappings.liabilities"
|
||||||
|
>
|
||||||
|
<ng-container matColumnDef="accountName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Liability Account</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="glCode">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
{{
|
||||||
|
formatGlAccountByCode(
|
||||||
|
row.glCode,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="['accountName', 'glCode']"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glCode']"></tr>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.otherGlMappings.expenses?.length) {
|
||||||
|
<table
|
||||||
|
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
||||||
|
mat-table
|
||||||
|
[dataSource]="savingProduct.otherGlMappings.expenses"
|
||||||
|
>
|
||||||
|
<ng-container matColumnDef="accountName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Expense Account</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="glCode">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
{{
|
||||||
|
formatGlAccountByCode(
|
||||||
|
row.glCode,
|
||||||
|
savingProductsTemplate?.accountingMappingOptions.expenseAccountOptions
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="['accountName', 'glCode']"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glCode']"></tr>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.otherGlMappings.income?.length) {
|
||||||
|
<table
|
||||||
|
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
||||||
|
mat-table
|
||||||
|
[dataSource]="savingProduct.otherGlMappings.income"
|
||||||
|
>
|
||||||
|
<ng-container matColumnDef="accountName">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Income Account</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="glCode">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
||||||
|
<td mat-cell *matCellDef="let row">
|
||||||
|
{{
|
||||||
|
formatGlAccountByCode(row.glCode, savingProductsTemplate?.accountingMappingOptions.incomeAccountOptions)
|
||||||
|
}}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="['accountName', 'glCode']"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glCode']"></tr>
|
||||||
|
</table>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (
|
@if (
|
||||||
savingProduct.advancedAccountingRules &&
|
savingProduct.advancedAccountingRules &&
|
||||||
@@ -265,7 +744,7 @@
|
|||||||
<td mat-cell *matCellDef="let paymentFundSource">
|
<td mat-cell *matCellDef="let paymentFundSource">
|
||||||
{{
|
{{
|
||||||
paymentFundSource.fundSourceAccountId
|
paymentFundSource.fundSourceAccountId
|
||||||
| find: savingProductsTemplate.accountingMappingOptions.assetAccountOptions : 'id' : 'name'
|
| find: savingProductsTemplate?.accountingMappingOptions.assetAccountOptions : 'id' : 'name'
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -289,7 +768,7 @@
|
|||||||
<td mat-cell *matCellDef="let feesIncome">
|
<td mat-cell *matCellDef="let feesIncome">
|
||||||
{{
|
{{
|
||||||
feesIncome.incomeAccountId
|
feesIncome.incomeAccountId
|
||||||
| find: savingProductsTemplate.accountingMappingOptions.incomeAccountOptions : 'id' : 'name'
|
| find: savingProductsTemplate?.accountingMappingOptions.incomeAccountOptions : 'id' : 'name'
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -317,7 +796,7 @@
|
|||||||
<td mat-cell *matCellDef="let penaltyIncome">
|
<td mat-cell *matCellDef="let penaltyIncome">
|
||||||
{{
|
{{
|
||||||
penaltyIncome.incomeAccountId
|
penaltyIncome.incomeAccountId
|
||||||
| find: savingProductsTemplate.accountingMappingOptions.incomeAccountOptions : 'id' : 'name'
|
| find: savingProductsTemplate?.accountingMappingOptions.incomeAccountOptions : 'id' : 'name'
|
||||||
}}
|
}}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -329,4 +808,3 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+92
@@ -71,3 +71,95 @@ mat-divider {
|
|||||||
.margin-t {
|
.margin-t {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spa-gl-summary,
|
||||||
|
.spa-other-gl-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.9rem;
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-gl-section h5 {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #91a0bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-table:first-of-type {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-summary .mat-elevation-z1 {
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-table th.mat-mdc-header-cell,
|
||||||
|
.spa-other-gl-table td.mat-mdc-cell {
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
padding-right: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-table th.mat-mdc-header-cell:first-child,
|
||||||
|
.spa-other-gl-table td.mat-mdc-cell:first-child {
|
||||||
|
width: 38%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-table th.mat-mdc-header-cell:last-child,
|
||||||
|
.spa-other-gl-table td.mat-mdc-cell:last-child {
|
||||||
|
width: 62%;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 768px) {
|
||||||
|
.spa-gl-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+137
-48
@@ -22,7 +22,6 @@ import {
|
|||||||
MatRowDef,
|
MatRowDef,
|
||||||
MatRow
|
MatRow
|
||||||
} from '@angular/material/table';
|
} from '@angular/material/table';
|
||||||
import { ViewSavingsAccountingDetailsComponent } from '../../../../shared/accounting/view-savings-accounting-details/view-savings-accounting-details.component';
|
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
import { FindPipe } from '../../../../pipes/find.pipe';
|
import { FindPipe } from '../../../../pipes/find.pipe';
|
||||||
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
|
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
|
||||||
@@ -46,7 +45,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
MatHeaderRow,
|
MatHeaderRow,
|
||||||
MatRowDef,
|
MatRowDef,
|
||||||
MatRow,
|
MatRow,
|
||||||
ViewSavingsAccountingDetailsComponent,
|
|
||||||
FaIconComponent,
|
FaIconComponent,
|
||||||
FindPipe,
|
FindPipe,
|
||||||
FormatNumberPipe,
|
FormatNumberPipe,
|
||||||
@@ -79,6 +77,25 @@ export class SavingProductPreviewStepComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
accountingMappings: any = {};
|
accountingMappings: any = {};
|
||||||
accountingRule: OptionData;
|
accountingRule: OptionData;
|
||||||
|
interestCalculationTypeOptionData: any[] = [];
|
||||||
|
accountFacilityFeatureItems = [
|
||||||
|
{ key: 'atmFacility', label: 'ATM Facility' },
|
||||||
|
{ key: 'atmWithdrawalFacility', label: 'ATM Withdrawal Facility' },
|
||||||
|
{ key: 'upiFacility', label: 'UPI Facility' },
|
||||||
|
{ key: 'posTransactionsFacility', label: 'POS Transactions Facility' },
|
||||||
|
{ key: 'chequeBook', label: 'Cheque Book' },
|
||||||
|
{ key: 'impsFacility', label: 'IMPS Facility' },
|
||||||
|
{ key: 'smsAlerts', label: 'SMS Alerts' },
|
||||||
|
{ key: 'bbpsFacility', label: 'BBPS Facility' },
|
||||||
|
{ key: 'insuranceCover', label: 'Insurance Cover' },
|
||||||
|
{ key: 'bepgFacility', label: 'BEPG Facility' },
|
||||||
|
{ key: 'neftFacility', label: 'NEFT Facility' },
|
||||||
|
{ key: 'rtgsFacility', label: 'RTGS Facility' },
|
||||||
|
{ key: 'interBankTotalCapFacility', label: 'Inter-Bank Total Cap Facility' },
|
||||||
|
{ key: 'withdrawalFacility', label: 'Withdrawal Facility' },
|
||||||
|
{ key: 'depositFacilityKyc', label: 'Deposit Facility (KYC)' },
|
||||||
|
{ key: 'virtualDebitCard', label: 'Virtual Debit Card' }
|
||||||
|
];
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.setCurrentValues();
|
this.setCurrentValues();
|
||||||
@@ -89,62 +106,88 @@ export class SavingProductPreviewStepComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setCurrentValues(): void {
|
setCurrentValues(): void {
|
||||||
|
if (!this.savingProduct || !this.savingProductsTemplate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.interestCalculationTypeOptionData =
|
||||||
|
this.savingProductsTemplate.interestCalculationTypeOption ||
|
||||||
|
this.savingProductsTemplate.interestCalculationTypeOptions ||
|
||||||
|
[];
|
||||||
this.accountingRule = this.accounting.getAccountingRuleFrom(this.savingProduct.accountingRule);
|
this.accountingRule = this.accounting.getAccountingRuleFrom(this.savingProduct.accountingRule);
|
||||||
|
|
||||||
if (this.isCashOrAccrualAccounting()) {
|
if (!this.isCashOrAccrualAccounting()) {
|
||||||
const assetAccountData = this.savingProductsTemplate.accountingMappingOptions.assetAccountOptions || [];
|
return;
|
||||||
const incomeAccountData = this.savingProductsTemplate.accountingMappingOptions.incomeAccountOptions || [];
|
}
|
||||||
const expenseAccountData = this.savingProductsTemplate.accountingMappingOptions.expenseAccountOptions || [];
|
|
||||||
const liabilityAccountData = this.savingProductsTemplate.accountingMappingOptions.liabilityAccountOptions || [];
|
|
||||||
|
|
||||||
|
const assetAccountData = this.savingProductsTemplate?.accountingMappingOptions?.assetAccountOptions || [];
|
||||||
|
const incomeAccountData = this.savingProductsTemplate?.accountingMappingOptions?.incomeAccountOptions || [];
|
||||||
|
const expenseAccountData = this.savingProductsTemplate?.accountingMappingOptions?.expenseAccountOptions || [];
|
||||||
|
const liabilityAccountData = this.savingProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions || [];
|
||||||
|
|
||||||
|
this.accountingMappings = {
|
||||||
|
savingsReferenceAccount: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.savingsReferenceAccountId,
|
||||||
|
assetAccountData
|
||||||
|
),
|
||||||
|
overdraftPortfolioControl: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.overdraftPortfolioControlId,
|
||||||
|
assetAccountData
|
||||||
|
),
|
||||||
|
savingsControlAccount: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.savingsControlAccountId,
|
||||||
|
liabilityAccountData
|
||||||
|
),
|
||||||
|
transfersInSuspenseAccount: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.transfersInSuspenseAccountId,
|
||||||
|
liabilityAccountData
|
||||||
|
),
|
||||||
|
interestOnSavingsAccount: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.interestOnSavingsAccountId,
|
||||||
|
expenseAccountData
|
||||||
|
),
|
||||||
|
writeOffAccount: this.accounting.glAccountLookUp(this.savingProduct.writeOffAccountId, expenseAccountData),
|
||||||
|
incomeFromFeeAccount: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.incomeFromFeeAccountId,
|
||||||
|
incomeAccountData
|
||||||
|
),
|
||||||
|
incomeFromPenaltyAccount: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.incomeFromPenaltyAccountId,
|
||||||
|
incomeAccountData
|
||||||
|
),
|
||||||
|
incomeFromInterest: this.accounting.glAccountLookUp(this.savingProduct.incomeFromInterestId, incomeAccountData)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.isAccrualAccounting()) {
|
||||||
this.accountingMappings = {
|
this.accountingMappings = {
|
||||||
savingsReferenceAccount: this.accounting.glAccountLookUp(
|
...this.accountingMappings,
|
||||||
this.savingProduct.savingsReferenceAccountId,
|
feeReceivableAccount: this.accounting.glAccountLookUp(
|
||||||
assetAccountData
|
|
||||||
),
|
|
||||||
overdraftPortfolioControl: this.accounting.glAccountLookUp(
|
|
||||||
this.savingProduct.overdraftPortfolioControlId,
|
|
||||||
assetAccountData
|
|
||||||
),
|
|
||||||
savingsControlAccount: this.accounting.glAccountLookUp(
|
|
||||||
this.savingProduct.savingsControlAccountId,
|
|
||||||
liabilityAccountData
|
|
||||||
),
|
|
||||||
transfersInSuspenseAccount: this.accounting.glAccountLookUp(
|
|
||||||
this.savingProduct.transfersInSuspenseAccountId,
|
|
||||||
liabilityAccountData
|
|
||||||
),
|
|
||||||
escheatLiability: this.accounting.glAccountLookUp(this.savingProduct.escheatLiabilityId, liabilityAccountData),
|
|
||||||
interestOnSavingsAccount: this.accounting.glAccountLookUp(
|
|
||||||
this.savingProduct.interestOnSavingsAccountId,
|
|
||||||
expenseAccountData
|
|
||||||
),
|
|
||||||
writeOffAccount: this.accounting.glAccountLookUp(this.savingProduct.writeOffAccountId, expenseAccountData),
|
|
||||||
incomeFromFeeAccount: this.accounting.glAccountLookUp(
|
|
||||||
this.savingProduct.incomeFromFeeAccountId,
|
|
||||||
incomeAccountData
|
|
||||||
),
|
|
||||||
incomeFromPenaltyAccount: this.accounting.glAccountLookUp(
|
|
||||||
this.savingProduct.incomeFromPenaltyAccountId,
|
|
||||||
incomeAccountData
|
|
||||||
),
|
|
||||||
incomeFromInterest: this.accounting.glAccountLookUp(this.savingProduct.incomeFromInterestId, incomeAccountData)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.isAccrualAccounting()) {
|
|
||||||
this.accountingMappings['feesReceivableAccount'] = this.accounting.glAccountLookUp(
|
|
||||||
this.savingProduct.feesReceivableAccountId,
|
this.savingProduct.feesReceivableAccountId,
|
||||||
assetAccountData
|
assetAccountData
|
||||||
);
|
),
|
||||||
this.accountingMappings['penaltiesReceivableAccount'] = this.accounting.glAccountLookUp(
|
penaltyReceivableAccount: this.accounting.glAccountLookUp(
|
||||||
this.savingProduct.penaltiesReceivableAccountId,
|
this.savingProduct.penaltiesReceivableAccountId,
|
||||||
assetAccountData
|
assetAccountData
|
||||||
);
|
),
|
||||||
this.accountingMappings['interestPayableAccount'] = this.accounting.glAccountLookUp(
|
interestPayableAccount: this.accounting.glAccountLookUp(
|
||||||
this.savingProduct.interestPayableAccountId,
|
this.savingProduct.interestPayableAccountId,
|
||||||
liabilityAccountData
|
liabilityAccountData
|
||||||
);
|
),
|
||||||
}
|
interestReceivableAccount: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.interestReceivableAccountId,
|
||||||
|
assetAccountData
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.savingProduct.isDormancyTrackingActive) {
|
||||||
|
this.accountingMappings = {
|
||||||
|
...this.accountingMappings,
|
||||||
|
escheatLiabilityAccount: this.accounting.glAccountLookUp(
|
||||||
|
this.savingProduct.escheatLiabilityId,
|
||||||
|
liabilityAccountData
|
||||||
|
)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,4 +198,50 @@ export class SavingProductPreviewStepComponent implements OnInit, OnChanges {
|
|||||||
isAccrualAccounting(): boolean {
|
isAccrualAccounting(): boolean {
|
||||||
return this.accounting.isAccrualAccounting(this.accountingRule);
|
return this.accounting.isAccrualAccounting(this.accountingRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getBooleanLabel(value: any): string {
|
||||||
|
return value === true || value === 'Yes' ? 'Yes' : 'No';
|
||||||
|
}
|
||||||
|
|
||||||
|
getLockinPeriodFrequencyTypeLabel(value: any): string {
|
||||||
|
switch (value) {
|
||||||
|
case 'DAYS':
|
||||||
|
return 'Days';
|
||||||
|
case 'WEEKS':
|
||||||
|
return 'Weeks';
|
||||||
|
case 'MONTHS':
|
||||||
|
return 'Months';
|
||||||
|
case 'YEARS':
|
||||||
|
return 'Years';
|
||||||
|
default:
|
||||||
|
return value ?? '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptionLabel(value: any, options: any[], key: string, property: string): string {
|
||||||
|
if (value === null || value === undefined || !Array.isArray(options)) {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = options.find((option) => option?.[key] === value);
|
||||||
|
return match?.[property] || '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
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 '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = options.find((option) => option?.glCode === glCode);
|
||||||
|
return account ? `(${account.glCode}) ${account.name}` : glCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+77
-300
@@ -1,4 +1,4 @@
|
|||||||
<!--
|
<!--
|
||||||
Copyright since 2025 Mifos Initiative
|
Copyright since 2025 Mifos Initiative
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
@@ -7,326 +7,103 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<form [formGroup]="savingProductSettingsForm">
|
<form [formGroup]="savingProductSettingsForm">
|
||||||
<!-- Opening Balance -->
|
|
||||||
<div class="sps-section">
|
<div class="sps-section">
|
||||||
<div class="sps-section__header">
|
<div class="sps-section__header">
|
||||||
<i class="fas fa-wallet"></i>
|
<span class="sps-section__badge"><i class="fas fa-balance-scale"></i></span>
|
||||||
<h3>{{ 'labels.inputs.Opening Balance' | translate }}</h3>
|
<span>Balance & Minimum Balance</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sps-grid">
|
<div class="sps-body">
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
<div class="sps-layout sps-layout--balance">
|
||||||
<mat-label>{{ 'labels.inputs.Minimum Opening Balance' | translate }}</mat-label>
|
<div class="sps-row sps-row--three">
|
||||||
<input
|
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||||
type="number"
|
<mat-label>Minimum Opening Balance</mat-label>
|
||||||
matInput
|
<input type="number" matInput formControlName="minRequiredOpeningBalance" min="0" step="0.01" />
|
||||||
matTooltip="{{ 'tooltips.Sets the minimum deposit amount' | translate }}"
|
</mat-form-field>
|
||||||
formControlName="minRequiredOpeningBalance"
|
|
||||||
min="0"
|
<div class="sps-toggle sps-toggle--inline">
|
||||||
step="0.01"
|
<mat-checkbox labelPosition="before" formControlName="enforceMinRequiredBalance">
|
||||||
/>
|
<span>Enforce Minimum Balance</span>
|
||||||
@if (savingProductSettingsForm.get('minRequiredOpeningBalance').hasError('min')) {
|
</mat-checkbox>
|
||||||
<mat-error>
|
</div>
|
||||||
{{ 'labels.inputs.Minimum Opening Balance' | translate }} {{ 'labels.commons.must be' | translate }}
|
|
||||||
{{ 'labels.commons.a positive number' | translate }}
|
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||||
</mat-error>
|
<mat-label>Minimum Balance (₹)</mat-label>
|
||||||
}
|
<input type="number" matInput formControlName="minRequiredBalance" min="0" step="0.01" />
|
||||||
</mat-form-field>
|
<mat-hint>Penal charge applies on breach</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sps-row sps-row--two">
|
||||||
|
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||||
|
<mat-label>Balance Frequency</mat-label>
|
||||||
|
<mat-select formControlName="balanceFrequency">
|
||||||
|
@for (balanceFrequency of periodFrequencyData; track balanceFrequency.id) {
|
||||||
|
<mat-option [value]="balanceFrequency.value">{{ balanceFrequency.value }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<div class="sps-toggle sps-toggle--inline sps-toggle--right">
|
||||||
|
<mat-checkbox labelPosition="before" formControlName="withdrawalFeeForTransfers">
|
||||||
|
<span>Apply Withdrawal Fee for Transfers</span>
|
||||||
|
</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Lock-in Period -->
|
|
||||||
<div class="sps-section">
|
<div class="sps-section">
|
||||||
<div class="sps-section__header">
|
<div class="sps-section__header">
|
||||||
<i class="fas fa-lock"></i>
|
<span class="sps-section__badge"><i class="fas fa-lock"></i></span>
|
||||||
<h3>{{ 'labels.inputs.Lock-in Period' | translate }}</h3>
|
<span>Lock-in Period</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sps-grid">
|
<div class="sps-body">
|
||||||
<div class="sps-field--full sps-checkbox-row">
|
<div class="sps-lockin-row">
|
||||||
<mat-checkbox
|
<span>Enable Lock-in Period</span>
|
||||||
labelPosition="before"
|
<mat-checkbox formControlName="enableLockinPeriod"></mat-checkbox>
|
||||||
matTooltip="{{ 'tooltips.Used to indicate the length of time' | translate }}"
|
|
||||||
formControlName="enableLockinPeriod"
|
|
||||||
>
|
|
||||||
{{ 'labels.inputs.Enable Lock-in Period' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (savingProductSettingsForm.value.enableLockinPeriod) {
|
@if (savingProductSettingsForm.value.enableLockinPeriod) {
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
<div class="sps-lockin-fields">
|
||||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||||
<input
|
<mat-label>Frequency*</mat-label>
|
||||||
type="number"
|
<input type="number" matInput formControlName="lockinPeriodFrequency" min="1" step="1" />
|
||||||
matInput
|
</mat-form-field>
|
||||||
matTooltip="{{ 'tooltips.The number at which lock in period occurs' | translate }}"
|
|
||||||
formControlName="lockinPeriodFrequency"
|
|
||||||
min="1"
|
|
||||||
step="1"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
@if (savingProductSettingsForm.get('lockinPeriodFrequency')?.hasError('required')) {
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Frequency' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
}
|
|
||||||
@if (savingProductSettingsForm.get('lockinPeriodFrequency')?.hasError('min')) {
|
|
||||||
<mat-error>{{ 'tooltips.Frequency must be greater than zero' | translate }}</mat-error>
|
|
||||||
}
|
|
||||||
@if (savingProductSettingsForm.get('lockinPeriodFrequency')?.hasError('pattern')) {
|
|
||||||
<mat-error>{{ 'tooltips.Frequency must be a positive integer' | translate }}</mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||||
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
<mat-label>Type*</mat-label>
|
||||||
<mat-select formControlName="lockinPeriodFrequencyType" required>
|
<mat-select formControlName="lockinPeriodFrequencyType">
|
||||||
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
|
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
|
||||||
<mat-option [value]="lockinPeriodFrequencyType.id">
|
<mat-option [value]="lockinPeriodFrequencyType.id">
|
||||||
{{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }}
|
{{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
}
|
}
|
||||||
</mat-select>
|
</mat-select>
|
||||||
@if (savingProductSettingsForm.get('lockinPeriodFrequencyType')?.hasError('required')) {
|
</mat-form-field>
|
||||||
<mat-error>
|
</div>
|
||||||
{{ 'labels.inputs.Type' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Balance & Fees -->
|
|
||||||
<div class="sps-section">
|
<div class="sps-section">
|
||||||
<div class="sps-section__header">
|
<div class="sps-section__header">
|
||||||
<i class="fas fa-balance-scale"></i>
|
<span class="sps-section__badge"><i class="fas fa-cog"></i></span>
|
||||||
<h3>{{ 'labels.inputs.Balance' | translate }} & {{ 'labels.inputs.Fees' | translate }}</h3>
|
<span>Additional Rules</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sps-grid">
|
<div class="sps-body">
|
||||||
<div class="sps-field sps-checkbox-row">
|
<div class="sps-layout sps-layout--additional">
|
||||||
<mat-checkbox
|
<div class="sps-toggle sps-toggle--inline">
|
||||||
labelPosition="before"
|
<mat-checkbox formControlName="accountClosedZeroBalanceInDayClosing">
|
||||||
matTooltip="{{ 'tooltips.Indicates whether the withdrawal fee' | translate }}"
|
<span>Account Closed with Zero balance in Day Closing</span>
|
||||||
formControlName="withdrawalFeeForTransfers"
|
</mat-checkbox>
|
||||||
>
|
</div>
|
||||||
{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}
|
|
||||||
</mat-checkbox>
|
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||||
|
<mat-label>Balance Required for Interest Calculation</mat-label>
|
||||||
|
<input type="number" matInput formControlName="minBalanceForInterestCalculation" min="0" step="0.01" />
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Sets the balance required for interest calculation' | translate }}"
|
|
||||||
formControlName="minBalanceForInterestCalculation"
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
@if (savingProductSettingsForm.get('minBalanceForInterestCalculation').hasError('min')) {
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}
|
|
||||||
{{ 'labels.commons.must be' | translate }}
|
|
||||||
{{ 'labels.commons.a positive number' | translate }}
|
|
||||||
</mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<div class="sps-field sps-checkbox-row">
|
|
||||||
<mat-checkbox
|
|
||||||
labelPosition="before"
|
|
||||||
matTooltip="{{ 'tooltips.Indicates whether to enforce a minimum balance' | translate }}"
|
|
||||||
formControlName="enforceMinRequiredBalance"
|
|
||||||
>
|
|
||||||
{{ 'labels.inputs.Enforce Minimum Balance' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Minimum Balance' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Sets the minimum balance allowed for a saving account' | translate }}"
|
|
||||||
formControlName="minRequiredBalance"
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
@if (savingProductSettingsForm.get('minRequiredBalance').hasError('min')) {
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Minimum Balance' | translate }} {{ 'labels.commons.must be' | translate }}
|
|
||||||
{{ 'labels.commons.a positive number' | translate }}
|
|
||||||
</mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tax -->
|
|
||||||
<div class="sps-section">
|
|
||||||
<div class="sps-section__header">
|
|
||||||
<i class="fas fa-file-invoice-dollar"></i>
|
|
||||||
<h3>{{ 'labels.inputs.Tax' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="sps-grid">
|
|
||||||
<div class="sps-field--full sps-checkbox-row">
|
|
||||||
<mat-checkbox
|
|
||||||
labelPosition="before"
|
|
||||||
matTooltip="{{ 'tooltips.An boolean flag to attach' | translate }}"
|
|
||||||
formControlName="withHoldTax"
|
|
||||||
>
|
|
||||||
{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (savingProductSettingsForm.value.withHoldTax) {
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Tax Group' | translate }}</mat-label>
|
|
||||||
<mat-select formControlName="taxGroupId" required>
|
|
||||||
@for (taxGroup of taxGroupData; track taxGroup) {
|
|
||||||
<mat-option [value]="taxGroup.id">
|
|
||||||
{{ taxGroup.name }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Tax Group' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Overdraft -->
|
|
||||||
<div class="sps-section">
|
|
||||||
<div class="sps-section__header">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<h3>{{ 'labels.inputs.Overdraft' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="sps-grid">
|
|
||||||
<div class="sps-field--full sps-checkbox-row">
|
|
||||||
<mat-checkbox
|
|
||||||
labelPosition="before"
|
|
||||||
matTooltip="{{ 'tooltips.Indicates whether saving accounts based on this saving product' | translate }}"
|
|
||||||
formControlName="allowOverdraft"
|
|
||||||
>
|
|
||||||
{{ 'labels.inputs.Is Overdraft Allowed' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (savingProductSettingsForm.value.allowOverdraft) {
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Sets the overdraft required for interest calculation' | translate }}"
|
|
||||||
formControlName="minOverdraftForInterestCalculation"
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Default interest rate on overdraft' | translate }}"
|
|
||||||
formControlName="nominalAnnualInterestRateOverdraft"
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Sets the maximum allowed overdraft amount' | translate }}"
|
|
||||||
formControlName="overdraftLimit"
|
|
||||||
min="0"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dormancy Tracking -->
|
|
||||||
<div class="sps-section">
|
|
||||||
<div class="sps-section__header">
|
|
||||||
<i class="fas fa-bed"></i>
|
|
||||||
<h3>{{ 'labels.inputs.Dormancy Tracking' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="sps-grid">
|
|
||||||
<div class="sps-field--full sps-checkbox-row">
|
|
||||||
<mat-checkbox
|
|
||||||
labelPosition="before"
|
|
||||||
matTooltip="{{ 'tooltips.Enables definition and tracking inactive Savings Accounts' | translate }}"
|
|
||||||
formControlName="isDormancyTrackingActive"
|
|
||||||
>
|
|
||||||
{{ 'labels.inputs.Enable Dormancy Tracking' | translate }}
|
|
||||||
</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (savingProductSettingsForm.value.isDormancyTrackingActive) {
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Number of Days to Inactive sub-status' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Inactive period to mark an account as Inactive' | translate }}"
|
|
||||||
formControlName="daysToInactive"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Number of Days to Inactive sub-status' | translate }}
|
|
||||||
{{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Number of Days to Dormant sub-status' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Inactive period to mark an account as Dormant' | translate }}"
|
|
||||||
formControlName="daysToDormancy"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Number of Days to Dormant sub-status' | translate }}
|
|
||||||
{{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="sps-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Number of Days to Escheat' | translate }}</mat-label>
|
|
||||||
<input
|
|
||||||
type="number"
|
|
||||||
matInput
|
|
||||||
matTooltip="{{ 'tooltips.Inactive period to mark an account as Escheat' | translate }}"
|
|
||||||
formControlName="daysToEscheat"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Number of Days to Escheat' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+117
-10
@@ -44,15 +44,20 @@ $gray-900: #202124;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sps-grid {
|
.sps-section__badge {
|
||||||
display: grid;
|
display: inline-flex;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
align-items: center;
|
||||||
gap: 4px 16px;
|
justify-content: center;
|
||||||
padding: 16px 24px;
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #eff6ff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
.sps-body {
|
||||||
grid-template-columns: 1fr;
|
background: #fff;
|
||||||
}
|
padding: 18px 24px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sps-field {
|
.sps-field {
|
||||||
@@ -61,10 +66,112 @@ $gray-900: #202124;
|
|||||||
&--full {
|
&--full {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--compact {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sps-checkbox-row {
|
.sps-layout {
|
||||||
|
display: grid;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-layout--balance {
|
||||||
|
gap: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-layout--lockin {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|
||||||
|
@media (width <= 640px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-layout--additional {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|
||||||
|
@media (width <= 1100px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-row {
|
||||||
|
display: grid;
|
||||||
|
gap: 18px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-row--three {
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.sps-field--compact:first-child {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-toggle--inline {
|
||||||
|
align-self: center;
|
||||||
|
min-height: 56px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-row--two {
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.sps-field--compact {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-toggle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px 0;
|
min-height: 56px;
|
||||||
|
|
||||||
|
mat-checkbox {
|
||||||
|
font-size: 14px;
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--full {
|
||||||
|
min-height: auto;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--inline {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--right {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-lockin-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid $gray-200;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-lockin-row mat-checkbox {
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sps-lockin-fields {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
@media (width <= 640px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+67
-111
@@ -1,4 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright since 2025 Mifos Initiative
|
* Copyright since 2025 Mifos Initiative
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
@@ -7,16 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||||
import {
|
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||||
UntypedFormGroup,
|
|
||||||
UntypedFormBuilder,
|
|
||||||
UntypedFormControl,
|
|
||||||
Validators,
|
|
||||||
ReactiveFormsModule
|
|
||||||
} from '@angular/forms';
|
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
|
||||||
import { MatCheckbox } from '@angular/material/checkbox';
|
import { MatCheckbox } from '@angular/material/checkbox';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -25,9 +17,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
styleUrls: ['./saving-product-settings-step.component.scss'],
|
styleUrls: ['./saving-product-settings-step.component.scss'],
|
||||||
imports: [
|
imports: [
|
||||||
...STANDALONE_SHARED_IMPORTS,
|
...STANDALONE_SHARED_IMPORTS,
|
||||||
MatTooltip,
|
MatCheckbox
|
||||||
MatCheckbox,
|
|
||||||
FaIconComponent
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class SavingProductSettingsStepComponent implements OnInit {
|
export class SavingProductSettingsStepComponent implements OnInit {
|
||||||
@@ -37,8 +27,26 @@ export class SavingProductSettingsStepComponent implements OnInit {
|
|||||||
|
|
||||||
savingProductSettingsForm: UntypedFormGroup;
|
savingProductSettingsForm: UntypedFormGroup;
|
||||||
|
|
||||||
lockinPeriodFrequencyTypeData: any;
|
periodFrequencyData: any[] = [];
|
||||||
taxGroupData: any;
|
|
||||||
|
lockinPeriodFrequencyTypeData = [
|
||||||
|
{
|
||||||
|
id: 'DAYS',
|
||||||
|
value: 'value.days'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'WEEKS',
|
||||||
|
value: 'value.weeks'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'MONTHS',
|
||||||
|
value: 'value.months'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'YEARS',
|
||||||
|
value: 'value.years'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.createSavingProductSettingsForm();
|
this.createSavingProductSettingsForm();
|
||||||
@@ -46,32 +54,34 @@ export class SavingProductSettingsStepComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.lockinPeriodFrequencyTypeData = this.savingProductsTemplate.lockinPeriodFrequencyTypeOptions;
|
this.periodFrequencyData = this.savingProductsTemplate.periodFrequencyOptions || [];
|
||||||
this.taxGroupData = this.savingProductsTemplate.taxGroupOptions;
|
const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly';
|
||||||
|
const isEditing = !!this.savingProductsTemplate.id;
|
||||||
const hasLockinPeriod =
|
|
||||||
this.savingProductsTemplate.lockinPeriodFrequency && this.savingProductsTemplate.lockinPeriodFrequency > 0;
|
|
||||||
|
|
||||||
this.savingProductSettingsForm.patchValue({
|
this.savingProductSettingsForm.patchValue({
|
||||||
minRequiredOpeningBalance: this.savingProductsTemplate.minRequiredOpeningBalance,
|
minRequiredOpeningBalance: this.savingProductsTemplate.minRequiredOpeningBalance,
|
||||||
enableLockinPeriod: hasLockinPeriod,
|
|
||||||
withdrawalFeeForTransfers: this.savingProductsTemplate.withdrawalFeeForTransfers,
|
withdrawalFeeForTransfers: this.savingProductsTemplate.withdrawalFeeForTransfers,
|
||||||
minBalanceForInterestCalculation: this.savingProductsTemplate.minBalanceForInterestCalculation,
|
minBalanceForInterestCalculation: this.savingProductsTemplate.minBalanceForInterestCalculation,
|
||||||
enforceMinRequiredBalance: this.savingProductsTemplate.enforceMinRequiredBalance,
|
enforceMinRequiredBalance: this.savingProductsTemplate.enforceMinRequiredBalance,
|
||||||
minRequiredBalance: this.savingProductsTemplate.minRequiredBalance,
|
minRequiredBalance: this.savingProductsTemplate.minRequiredBalance,
|
||||||
allowOverdraft: this.savingProductsTemplate.allowOverdraft,
|
balanceFrequency: isEditing ? this.savingProductsTemplate.balanceFrequency || defaultPeriod : defaultPeriod,
|
||||||
minOverdraftForInterestCalculation: this.savingProductsTemplate.minOverdraftForInterestCalculation,
|
enableLockinPeriod: isEditing
|
||||||
nominalAnnualInterestRateOverdraft: this.savingProductsTemplate.nominalAnnualInterestRateOverdraft,
|
? this.savingProductsTemplate.lockinPeriodFrequency && this.savingProductsTemplate.lockinPeriodFrequency > 0
|
||||||
overdraftLimit: this.savingProductsTemplate.overdraftLimit,
|
: this.savingProductsTemplate.lockinPeriodFrequency && this.savingProductsTemplate.lockinPeriodFrequency > 0,
|
||||||
withHoldTax: this.savingProductsTemplate.withHoldTax,
|
accountClosedZeroBalanceInDayClosing: isEditing
|
||||||
taxGroupId: this.savingProductsTemplate.taxGroup && this.savingProductsTemplate.taxGroup.id,
|
? (this.savingProductsTemplate.accountClosedZeroBalanceInDayClosing ?? false)
|
||||||
isDormancyTrackingActive: this.savingProductsTemplate.isDormancyTrackingActive,
|
: false,
|
||||||
daysToInactive: this.savingProductsTemplate.daysToInactive,
|
isDormancyTrackingActive: isEditing ? (this.savingProductsTemplate.isDormancyTrackingActive ?? true) : true,
|
||||||
daysToDormancy: this.savingProductsTemplate.daysToDormancy,
|
daysToInactive: isEditing ? this.savingProductsTemplate.daysToInactive || 30 : 30,
|
||||||
daysToEscheat: this.savingProductsTemplate.daysToEscheat
|
daysToDormancy: isEditing ? this.savingProductsTemplate.daysToDormancy || 60 : 60,
|
||||||
|
daysToEscheat: isEditing ? this.savingProductsTemplate.daysToEscheat || 90 : 90
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasLockinPeriod) {
|
if (
|
||||||
|
isEditing &&
|
||||||
|
this.savingProductsTemplate.lockinPeriodFrequency &&
|
||||||
|
this.savingProductsTemplate.lockinPeriodFrequency > 0
|
||||||
|
) {
|
||||||
this.savingProductSettingsForm.patchValue({
|
this.savingProductSettingsForm.patchValue({
|
||||||
lockinPeriodFrequency: this.savingProductsTemplate.lockinPeriodFrequency,
|
lockinPeriodFrequency: this.savingProductsTemplate.lockinPeriodFrequency,
|
||||||
lockinPeriodFrequencyType:
|
lockinPeriodFrequencyType:
|
||||||
@@ -87,7 +97,6 @@ export class SavingProductSettingsStepComponent implements OnInit {
|
|||||||
'',
|
'',
|
||||||
[Validators.min(0)]
|
[Validators.min(0)]
|
||||||
],
|
],
|
||||||
enableLockinPeriod: [false],
|
|
||||||
withdrawalFeeForTransfers: [false],
|
withdrawalFeeForTransfers: [false],
|
||||||
minBalanceForInterestCalculation: [
|
minBalanceForInterestCalculation: [
|
||||||
'',
|
'',
|
||||||
@@ -98,95 +107,42 @@ export class SavingProductSettingsStepComponent implements OnInit {
|
|||||||
'',
|
'',
|
||||||
[Validators.min(0)]
|
[Validators.min(0)]
|
||||||
],
|
],
|
||||||
allowOverdraft: [false],
|
balanceFrequency: ['Monthly'],
|
||||||
withHoldTax: [false],
|
enableLockinPeriod: [false],
|
||||||
isDormancyTrackingActive: [false]
|
lockinPeriodFrequency: [''],
|
||||||
|
lockinPeriodFrequencyType: [''],
|
||||||
|
accountClosedZeroBalanceInDayClosing: [false],
|
||||||
|
// Dormancy tracking fields (required when isDormancyTrackingActive is true)
|
||||||
|
isDormancyTrackingActive: [true],
|
||||||
|
daysToInactive: [30],
|
||||||
|
daysToDormancy: [60],
|
||||||
|
daysToEscheat: [90]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setConditionalControls() {
|
setConditionalControls() {
|
||||||
this.savingProductSettingsForm.get('enableLockinPeriod').valueChanges.subscribe((enableLockinPeriod: any) => {
|
this.savingProductSettingsForm.get('enableLockinPeriod').valueChanges.subscribe((enableLockinPeriod: any) => {
|
||||||
if (enableLockinPeriod) {
|
if (enableLockinPeriod) {
|
||||||
this.savingProductSettingsForm.addControl(
|
this.savingProductSettingsForm.get('lockinPeriodFrequency').setValidators([
|
||||||
'lockinPeriodFrequency',
|
Validators.required,
|
||||||
new UntypedFormControl('', [
|
Validators.min(1),
|
||||||
Validators.required,
|
Validators.pattern('^[1-9]\\d*$')
|
||||||
Validators.min(1),
|
]);
|
||||||
Validators.pattern('^[1-9]\\d*$')
|
this.savingProductSettingsForm.get('lockinPeriodFrequencyType').setValidators([
|
||||||
])
|
Validators.required
|
||||||
);
|
]);
|
||||||
this.savingProductSettingsForm.addControl(
|
|
||||||
'lockinPeriodFrequencyType',
|
|
||||||
new UntypedFormControl('', Validators.required)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.savingProductSettingsForm.removeControl('lockinPeriodFrequency');
|
this.savingProductSettingsForm.get('lockinPeriodFrequency').setValue('');
|
||||||
this.savingProductSettingsForm.removeControl('lockinPeriodFrequencyType');
|
this.savingProductSettingsForm.get('lockinPeriodFrequencyType').setValue('');
|
||||||
|
this.savingProductSettingsForm.get('lockinPeriodFrequency').clearValidators();
|
||||||
|
this.savingProductSettingsForm.get('lockinPeriodFrequencyType').clearValidators();
|
||||||
}
|
}
|
||||||
|
this.savingProductSettingsForm.get('lockinPeriodFrequency').updateValueAndValidity();
|
||||||
|
this.savingProductSettingsForm.get('lockinPeriodFrequencyType').updateValueAndValidity();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.savingProductSettingsForm.get('allowOverdraft').valueChanges.subscribe((allowOverdraft: any) => {
|
|
||||||
if (allowOverdraft) {
|
|
||||||
this.savingProductSettingsForm.addControl(
|
|
||||||
'minOverdraftForInterestCalculation',
|
|
||||||
new UntypedFormControl('', [Validators.min(0)])
|
|
||||||
);
|
|
||||||
this.savingProductSettingsForm.addControl(
|
|
||||||
'nominalAnnualInterestRateOverdraft',
|
|
||||||
new UntypedFormControl('', [Validators.min(0)])
|
|
||||||
);
|
|
||||||
this.savingProductSettingsForm.addControl('overdraftLimit', new UntypedFormControl('', [Validators.min(0)]));
|
|
||||||
} else {
|
|
||||||
this.savingProductSettingsForm.removeControl('minOverdraftForInterestCalculation');
|
|
||||||
this.savingProductSettingsForm.removeControl('nominalAnnualInterestRateOverdraft');
|
|
||||||
this.savingProductSettingsForm.removeControl('overdraftLimit');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.savingProductSettingsForm.get('withHoldTax').valueChanges.subscribe((withHoldTax: any) => {
|
|
||||||
if (withHoldTax) {
|
|
||||||
this.savingProductSettingsForm.addControl('taxGroupId', new UntypedFormControl('', Validators.required));
|
|
||||||
} else {
|
|
||||||
this.savingProductSettingsForm.removeControl('taxGroupId');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.savingProductSettingsForm
|
|
||||||
.get('isDormancyTrackingActive')
|
|
||||||
.valueChanges.subscribe((isDormancyTrackingActive: any) => {
|
|
||||||
if (isDormancyTrackingActive) {
|
|
||||||
this.savingProductSettingsForm.addControl(
|
|
||||||
'daysToInactive',
|
|
||||||
new UntypedFormControl('', [
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(0)
|
|
||||||
])
|
|
||||||
);
|
|
||||||
this.savingProductSettingsForm.addControl(
|
|
||||||
'daysToDormancy',
|
|
||||||
new UntypedFormControl('', [
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(0)
|
|
||||||
])
|
|
||||||
);
|
|
||||||
this.savingProductSettingsForm.addControl(
|
|
||||||
'daysToEscheat',
|
|
||||||
new UntypedFormControl('', [
|
|
||||||
Validators.required,
|
|
||||||
Validators.min(0)
|
|
||||||
])
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.savingProductSettingsForm.removeControl('daysToInactive');
|
|
||||||
this.savingProductSettingsForm.removeControl('daysToDormancy');
|
|
||||||
this.savingProductSettingsForm.removeControl('daysToEscheat');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get savingProductSettings() {
|
get savingProductSettings() {
|
||||||
const formValue = { ...this.savingProductSettingsForm.value };
|
return { ...this.savingProductSettingsForm.value };
|
||||||
delete formValue.enableLockinPeriod;
|
|
||||||
return formValue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+134
-106
@@ -9,120 +9,148 @@
|
|||||||
<form [formGroup]="savingProductTermsForm">
|
<form [formGroup]="savingProductTermsForm">
|
||||||
<div class="spt-section">
|
<div class="spt-section">
|
||||||
<div class="spt-section__header">
|
<div class="spt-section__header">
|
||||||
<i class="fas fa-percentage"></i>
|
<span class="spt-section__badge"><i class="fas fa-chart-line"></i></span>
|
||||||
<h3>{{ 'labels.inputs.Interest Rate' | translate }}</h3>
|
<span>Interest Rate & Posting Terms</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="spt-grid">
|
<div class="spt-body">
|
||||||
<mat-form-field appearance="outline" class="spt-field">
|
<div class="spt-banner">
|
||||||
<mat-label>{{ 'labels.inputs.Nominal Annual Interest' | translate }}</mat-label>
|
<span class="spt-banner__icon">ℹ️</span>
|
||||||
<input
|
<span>
|
||||||
type="number"
|
Savings Account base rate:
|
||||||
matInput
|
<b>{{ savingProductTermsForm.get('nominalAnnualInterestRate').value }}%</b>
|
||||||
matTooltip="{{ 'tooltips.The default interest rate set' | translate }}"
|
</span>
|
||||||
formControlName="nominalAnnualInterestRate"
|
</div>
|
||||||
required
|
|
||||||
min="0"
|
<div class="spt-grid spt-grid--three">
|
||||||
step="0.01"
|
<mat-form-field appearance="outline" class="spt-field">
|
||||||
/>
|
<mat-label>{{ 'labels.inputs.Nominal Annual Interest' | translate }}</mat-label>
|
||||||
@if (savingProductTermsForm.get('nominalAnnualInterestRate').hasError('required')) {
|
<input
|
||||||
|
type="number"
|
||||||
|
matInput
|
||||||
|
matTooltip="{{ 'tooltips.The default interest rate set' | translate }}"
|
||||||
|
formControlName="nominalAnnualInterestRate"
|
||||||
|
required
|
||||||
|
min="0"
|
||||||
|
step="0.01"
|
||||||
|
/>
|
||||||
|
@if (savingProductTermsForm.get('nominalAnnualInterestRate').hasError('required')) {
|
||||||
|
<mat-error>
|
||||||
|
{{ 'labels.inputs.Nominal Annual Interest' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
@if (savingProductTermsForm.get('nominalAnnualInterestRate').hasError('min')) {
|
||||||
|
<mat-error> {{ 'labels.inputs.Nominal Annual Interest' | translate }} must be zero or greater </mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spt-field">
|
||||||
|
<mat-label>Effective Date</mat-label>
|
||||||
|
<input matInput [matDatepicker]="effectiveDatePicker" formControlName="effectiveDate" />
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="effectiveDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #effectiveDatePicker></mat-datepicker>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spt-field">
|
||||||
|
<mat-label>{{ 'labels.inputs.Interest Calculation Type' | translate }}</mat-label>
|
||||||
|
<mat-select
|
||||||
|
formControlName="interestCalculationType"
|
||||||
|
matTooltip="{{ 'tooltips.The method used to calculate interest' | translate }}"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
@for (interestCalculationType of interestCalculationTypeOptionData; track interestCalculationType.id) {
|
||||||
|
<mat-option [value]="interestCalculationType.id">
|
||||||
|
{{ interestCalculationType.value | translateKey: 'catalogs' }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{ 'labels.inputs.Nominal Annual Interest' | translate }} {{ 'labels.commons.is' | translate }}
|
{{ 'labels.inputs.Interest Calculation Type' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
</mat-error>
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spt-grid spt-grid--three">
|
||||||
|
<mat-form-field appearance="outline" class="spt-field">
|
||||||
|
<mat-label>Interest Calculate</mat-label>
|
||||||
|
<mat-select formControlName="interestCalculateSelector" required>
|
||||||
|
@for (mode of interestCalculationTypeData; track mode.id) {
|
||||||
|
<mat-option [value]="mode.id">{{ mode.value }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
@if (savingProductTermsForm.value.interestCalculateSelector === 2) {
|
||||||
|
<mat-form-field appearance="outline" class="spt-field">
|
||||||
|
<mat-label>Average Period</mat-label>
|
||||||
|
<mat-select formControlName="averagePeriod">
|
||||||
|
@for (period of periodFrequencyData; track period.id) {
|
||||||
|
<mat-option [value]="period.value">{{ period.value }}</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
}
|
}
|
||||||
@if (savingProductTermsForm.get('nominalAnnualInterestRate').hasError('min')) {
|
|
||||||
|
<mat-form-field appearance="outline" class="spt-field">
|
||||||
|
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
|
||||||
|
<mat-select
|
||||||
|
formControlName="interestPostingPeriodType"
|
||||||
|
matTooltip="{{ 'tooltips.Interest rate is posted or credited to a saving account' | translate }}"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
|
||||||
|
<mat-option [value]="interestPostingPeriodType.id">
|
||||||
|
{{ interestPostingPeriodType.value | translateKey: 'catalogs' }}
|
||||||
|
</mat-option>
|
||||||
|
}
|
||||||
|
</mat-select>
|
||||||
<mat-error>
|
<mat-error>
|
||||||
{{ 'labels.inputs.Nominal Annual Interest' | translate }} must be zero or greater
|
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
|
||||||
|
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||||
</mat-error>
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="spt-footer">
|
||||||
|
<div class="spt-footer__grid">
|
||||||
|
<div class="spt-toggle-card">
|
||||||
|
<div>
|
||||||
|
<h4>Auto Interest Post</h4>
|
||||||
|
</div>
|
||||||
|
<div class="spt-toggle-card__choices">
|
||||||
|
@for (option of yesNoOptions; track option.value) {
|
||||||
|
<label class="inline-flex items-center cursor-pointer">
|
||||||
|
<input type="radio" class="w-4 h-4" formControlName="autoPost" [value]="option.value" />
|
||||||
|
<span class="ml-2 text-sm">{{ option.label }}</span>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spt-field">
|
||||||
|
<mat-label>Frequency (Fix Day)</mat-label>
|
||||||
|
<input type="number" matInput formControlName="frequencyFixDay" />
|
||||||
|
<mat-hint>Days</mat-hint>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field appearance="outline" class="spt-field">
|
||||||
|
<mat-label>Last Interest Date</mat-label>
|
||||||
|
<input matInput [matDatepicker]="lastInterestDatePicker" formControlName="lastInterestDate" />
|
||||||
|
<mat-datepicker-toggle matSuffix [for]="lastInterestDatePicker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #lastInterestDatePicker></mat-datepicker>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProductTermsForm.value.interestCalculateSelector === 1) {
|
||||||
|
<div class="spt-note">
|
||||||
|
<span>⚠️</span>
|
||||||
|
<span
|
||||||
|
>Daily product-based interest accrual enabled - interest calculated EOD on daily closing balance.</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</mat-form-field>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spt-section">
|
|
||||||
<div class="spt-section__header">
|
|
||||||
<i class="fas fa-sliders-h"></i>
|
|
||||||
<h3>{{ 'labels.inputs.Interest Compounding' | translate }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="spt-grid">
|
|
||||||
<mat-form-field appearance="outline" class="spt-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Interest Compounding Period' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="interestCompoundingPeriodType"
|
|
||||||
matTooltip="{{ 'tooltips.The period at which interest rate' | translate }}"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
@for (interestCompoundingPeriodType of interestCompoundingPeriodTypeData; track interestCompoundingPeriodType) {
|
|
||||||
<mat-option [value]="interestCompoundingPeriodType.id">
|
|
||||||
{{ interestCompoundingPeriodType.value | translateKey: 'catalogs' }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="spt-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="interestPostingPeriodType"
|
|
||||||
matTooltip="{{ 'tooltips.Interest rate is posted or credited to a saving account' | translate }}"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
@for (interestPostingPeriodType of interestPostingPeriodTypeData; track interestPostingPeriodType) {
|
|
||||||
<mat-option [value]="interestPostingPeriodType.id">
|
|
||||||
{{ interestPostingPeriodType.value | translateKey: 'catalogs' }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Interest Posting Period' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="spt-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Interest Calculated using' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="interestCalculationType"
|
|
||||||
matTooltip="{{ 'tooltips.The method used to calculate interest' | translate }}"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
@for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) {
|
|
||||||
<mat-option [value]="interestCalculationType.id">
|
|
||||||
{{ interestCalculationType.value | translateKey: 'catalogs' }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="spt-field">
|
|
||||||
<mat-label>{{ 'labels.inputs.Days in Year' | translate }}</mat-label>
|
|
||||||
<mat-select
|
|
||||||
formControlName="interestCalculationDaysInYearType"
|
|
||||||
matTooltip="{{ 'tooltips.To calculate interest' | translate }}"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
@for (
|
|
||||||
interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData;
|
|
||||||
track interestCalculationDaysInYearType
|
|
||||||
) {
|
|
||||||
<mat-option [value]="interestCalculationDaysInYearType.id">
|
|
||||||
{{ interestCalculationDaysInYearType.value | translateKey: 'catalogs' }}
|
|
||||||
</mat-option>
|
|
||||||
}
|
|
||||||
</mat-select>
|
|
||||||
<mat-error>
|
|
||||||
{{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }}
|
|
||||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
|
||||||
</mat-error>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
+84
-1
@@ -44,17 +44,36 @@ $gray-900: #202124;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spt-section__badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: #eff6ff;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spt-body {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.spt-grid {
|
.spt-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 4px 16px;
|
gap: 4px 16px;
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (width <= 600px) {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spt-grid--three {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
.spt-field {
|
.spt-field {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
@@ -62,3 +81,67 @@ $gray-900: #202124;
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spt-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 16px 24px 0;
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: #eff6ff;
|
||||||
|
border: 1px solid #dbeafe;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #1d4ed8;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spt-footer {
|
||||||
|
padding: 0 24px 24px;
|
||||||
|
border-top: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spt-footer__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
padding-top: 18px;
|
||||||
|
|
||||||
|
@media (width <= 900px) {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spt-toggle-card {
|
||||||
|
padding: 12px 0;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: $gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__choices {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spt-note {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border: 1px solid #fde68a;
|
||||||
|
background: #fffbeb;
|
||||||
|
color: #b45309;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|||||||
+132
-8
@@ -10,6 +10,8 @@ import { Component, OnInit, Input, inject } from '@angular/core';
|
|||||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatTooltip } from '@angular/material/tooltip';
|
import { MatTooltip } from '@angular/material/tooltip';
|
||||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||||
|
import { Dates } from 'app/core/utils/dates';
|
||||||
|
import { SettingsService } from 'app/settings/settings.service';
|
||||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -24,6 +26,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
|||||||
})
|
})
|
||||||
export class SavingProductTermsStepComponent implements OnInit {
|
export class SavingProductTermsStepComponent implements OnInit {
|
||||||
private formBuilder = inject(UntypedFormBuilder);
|
private formBuilder = inject(UntypedFormBuilder);
|
||||||
|
private dateUtils = inject(Dates);
|
||||||
|
private settingsService = inject(SettingsService);
|
||||||
|
|
||||||
@Input() savingProductsTemplate: any;
|
@Input() savingProductsTemplate: any;
|
||||||
|
|
||||||
@@ -32,8 +36,22 @@ export class SavingProductTermsStepComponent implements OnInit {
|
|||||||
interestCompoundingPeriodTypeData: any;
|
interestCompoundingPeriodTypeData: any;
|
||||||
interestPostingPeriodTypeData: any;
|
interestPostingPeriodTypeData: any;
|
||||||
interestCalculationTypeData: any;
|
interestCalculationTypeData: any;
|
||||||
|
interestCalculationTypeOptionData: any;
|
||||||
interestCalculationDaysInYearTypeData: any;
|
interestCalculationDaysInYearTypeData: any;
|
||||||
|
periodFrequencyData: any[] = [];
|
||||||
|
yesNoOptions: Array<{
|
||||||
|
value: boolean;
|
||||||
|
label: string;
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
label: 'Yes'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
label: 'No'
|
||||||
|
}
|
||||||
|
];
|
||||||
constructor() {
|
constructor() {
|
||||||
this.createSavingProductTermsForm();
|
this.createSavingProductTermsForm();
|
||||||
}
|
}
|
||||||
@@ -42,15 +60,91 @@ export class SavingProductTermsStepComponent implements OnInit {
|
|||||||
this.interestCompoundingPeriodTypeData = this.savingProductsTemplate.interestCompoundingPeriodTypeOptions;
|
this.interestCompoundingPeriodTypeData = this.savingProductsTemplate.interestCompoundingPeriodTypeOptions;
|
||||||
this.interestPostingPeriodTypeData = this.savingProductsTemplate.interestPostingPeriodTypeOptions;
|
this.interestPostingPeriodTypeData = this.savingProductsTemplate.interestPostingPeriodTypeOptions;
|
||||||
this.interestCalculationTypeData = this.savingProductsTemplate.interestCalculationTypeOptions;
|
this.interestCalculationTypeData = this.savingProductsTemplate.interestCalculationTypeOptions;
|
||||||
|
this.interestCalculationTypeOptionData =
|
||||||
|
this.savingProductsTemplate.interestCalculationTypeOption ||
|
||||||
|
this.savingProductsTemplate.interestCalculationTypeOptions;
|
||||||
this.interestCalculationDaysInYearTypeData = this.savingProductsTemplate.interestCalculationDaysInYearTypeOptions;
|
this.interestCalculationDaysInYearTypeData = this.savingProductsTemplate.interestCalculationDaysInYearTypeOptions;
|
||||||
|
this.periodFrequencyData = this.savingProductsTemplate.periodFrequencyOptions || [];
|
||||||
|
this.setYesNoOptions(this.savingProductsTemplate?.yesNoOptions);
|
||||||
|
|
||||||
|
const isEditing = !!this.savingProductsTemplate.id;
|
||||||
|
const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly';
|
||||||
|
|
||||||
this.savingProductTermsForm.patchValue({
|
this.savingProductTermsForm.patchValue({
|
||||||
nominalAnnualInterestRate: this.savingProductsTemplate.nominalAnnualInterestRate,
|
nominalAnnualInterestRate: isEditing
|
||||||
interestCompoundingPeriodType: this.savingProductsTemplate.interestCompoundingPeriodType.id,
|
? this.savingProductsTemplate.nominalAnnualInterestRate
|
||||||
interestPostingPeriodType: this.savingProductsTemplate.interestPostingPeriodType.id,
|
: this.savingProductsTemplate.nominalAnnualInterestRate,
|
||||||
interestCalculationType: this.savingProductsTemplate.interestCalculationType.id,
|
interestCompoundingPeriodType: isEditing
|
||||||
interestCalculationDaysInYearType: this.savingProductsTemplate.interestCalculationDaysInYearType.id
|
? this.savingProductsTemplate.interestCompoundingPeriodType?.id
|
||||||
|
: this.savingProductsTemplate.interestCompoundingPeriodType?.id,
|
||||||
|
interestPostingPeriodType: isEditing
|
||||||
|
? this.savingProductsTemplate.interestPostingPeriodType?.id
|
||||||
|
: this.savingProductsTemplate.interestPostingPeriodType?.id,
|
||||||
|
interestCalculationType: isEditing
|
||||||
|
? this.savingProductsTemplate.interestCalculationTypeOption?.id ||
|
||||||
|
this.interestCalculationTypeOptionData?.[0]?.id ||
|
||||||
|
1
|
||||||
|
: this.interestCalculationTypeOptionData?.[0]?.id || 1,
|
||||||
|
interestCalculationDaysInYearType: isEditing
|
||||||
|
? this.savingProductsTemplate.interestCalculationDaysInYearType?.id
|
||||||
|
: this.savingProductsTemplate.interestCalculationDaysInYearType?.id,
|
||||||
|
effectiveDate: this.savingProductsTemplate.effectiveDate
|
||||||
|
? new Date(this.savingProductsTemplate.effectiveDate)
|
||||||
|
: '',
|
||||||
|
interestCalculateSelector: isEditing
|
||||||
|
? this.savingProductsTemplate.interestCalculateSelector || 1
|
||||||
|
: this.savingProductsTemplate.interestCalculationType?.id || this.interestCalculationTypeData?.[0]?.id || 1,
|
||||||
|
averagePeriod: isEditing ? this.savingProductsTemplate.averagePeriod || defaultPeriod : defaultPeriod,
|
||||||
|
autoPost: isEditing ? (this.savingProductsTemplate.autoPost ?? true) : true,
|
||||||
|
frequencyFixDay: isEditing ? this.savingProductsTemplate.frequencyFixDay || 30 : 30,
|
||||||
|
lastInterestDate: this.savingProductsTemplate.lastInterestDate
|
||||||
|
? new Date(this.savingProductsTemplate.lastInterestDate)
|
||||||
|
: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Map code value IDs to backend enum values for interestCalculationType
|
||||||
|
// Backend expects: 1=Daily Balance, 2=Average Daily Balance
|
||||||
|
const currentInterestCalcType = this.savingProductTermsForm.get('interestCalculationType').value;
|
||||||
|
if (currentInterestCalcType && currentInterestCalcType > 2) {
|
||||||
|
// If it's a code value ID (like 340, 341), map it to backend enum
|
||||||
|
// Default to 1 (Daily Balance) for custom code values
|
||||||
|
this.savingProductTermsForm.patchValue({
|
||||||
|
interestCalculationType: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setYesNoOptions(options: any[]) {
|
||||||
|
if (!Array.isArray(options)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedOptions = options
|
||||||
|
.map((option: any) => {
|
||||||
|
const rawValue = option?.name ?? option?.value ?? option;
|
||||||
|
const normalized = typeof rawValue === 'string' ? rawValue.trim().toUpperCase() : rawValue;
|
||||||
|
|
||||||
|
if (normalized === 'YES') {
|
||||||
|
return {
|
||||||
|
value: true,
|
||||||
|
label: 'Yes'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === 'NO') {
|
||||||
|
return {
|
||||||
|
value: false,
|
||||||
|
label: 'No'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((value: any) => !!value);
|
||||||
|
|
||||||
|
if (mappedOptions.length) {
|
||||||
|
this.yesNoOptions = mappedOptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createSavingProductTermsForm() {
|
createSavingProductTermsForm() {
|
||||||
@@ -77,11 +171,41 @@ export class SavingProductTermsStepComponent implements OnInit {
|
|||||||
interestCalculationDaysInYearType: [
|
interestCalculationDaysInYearType: [
|
||||||
'',
|
'',
|
||||||
Validators.required
|
Validators.required
|
||||||
]
|
],
|
||||||
|
effectiveDate: [''],
|
||||||
|
interestCalculateSelector: [1],
|
||||||
|
averagePeriod: ['Monthly'],
|
||||||
|
autoPost: [true],
|
||||||
|
frequencyFixDay: [
|
||||||
|
30,
|
||||||
|
[Validators.min(0)]
|
||||||
|
],
|
||||||
|
lastInterestDate: ['']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get savingProductTerms() {
|
get savingProductTerms() {
|
||||||
return this.savingProductTermsForm.value;
|
const formValue = { ...this.savingProductTermsForm.value };
|
||||||
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
|
const prevEffectiveDate: Date = this.savingProductTermsForm.value.effectiveDate;
|
||||||
|
const prevLastInterestDate: Date = this.savingProductTermsForm.value.lastInterestDate;
|
||||||
|
|
||||||
|
if (formValue.effectiveDate instanceof Date) {
|
||||||
|
formValue.effectiveDate = this.dateUtils.formatDate(prevEffectiveDate, dateFormat) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formValue.lastInterestDate instanceof Date) {
|
||||||
|
formValue.lastInterestDate = this.dateUtils.formatDate(prevLastInterestDate, dateFormat) || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert interestCalculationType from code value ID to backend enum value
|
||||||
|
// Backend expects: 1=Daily Balance, 2=Average Daily Balance
|
||||||
|
if (formValue.interestCalculationType && formValue.interestCalculationType > 2) {
|
||||||
|
formValue.interestCalculationType = 1; // Default to Daily Balance
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...formValue
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export class SavingProductResolver {
|
|||||||
private productsService = inject(ProductsService);
|
private productsService = inject(ProductsService);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the saving product data.
|
* Returns the saving product data with template options for dropdown lookups.
|
||||||
* @returns {Observable<any>}
|
* @returns {Observable<any>}
|
||||||
*/
|
*/
|
||||||
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
resolve(route: ActivatedRouteSnapshot): Observable<any> {
|
||||||
|
|||||||
+488
-278
@@ -18,304 +18,514 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-container">
|
<div class="responsive-column">
|
||||||
<div class="responsive-column">
|
<h2 class="mat-h2 flex-100">{{ savingProduct.name }}</h2>
|
||||||
<h2 class="mat-h2 flex-100">{{ savingProduct.name }}</h2>
|
|
||||||
|
|
||||||
<mat-divider [inset]="true"></mat-divider>
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Details' | translate }}</h3>
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Details' | translate }}</h3>
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Short Name' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.shortName }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-divider [inset]="true"></mat-divider>
|
@if (savingProduct.description) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
<div class="flex-fill layout-row">
|
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
||||||
<span class="flex-40">{{ 'labels.inputs.Short Name' | translate }}:</span>
|
<span class="flex-60">{{ savingProduct.description }}</span>
|
||||||
<span class="flex-60">{{ savingProduct.shortName }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (savingProduct.description) {
|
@if (
|
||||||
<div class="flex-fill layout-row">
|
savingProduct.accountType ||
|
||||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
savingProduct.customerSegment ||
|
||||||
<span class="flex-60">{{ savingProduct.description }}</span>
|
savingProduct.productCode ||
|
||||||
|
savingProduct.effectiveDate ||
|
||||||
|
savingProduct.effectiveTo ||
|
||||||
|
savingProduct.endDate ||
|
||||||
|
savingProduct.introducerRequired !== undefined ||
|
||||||
|
savingProduct.shareLinkingEnabled !== undefined
|
||||||
|
) {
|
||||||
|
<h3 class="mat-h3 flex-100">Product Classification</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
|
||||||
|
@if (savingProduct.accountType) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Account Type:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.accountType }}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-fill">{{ 'labels.inputs.Currency' | translate }}</h3>
|
@if (savingProduct.customerSegment) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
<mat-divider [inset]="true"></mat-divider>
|
<span class="flex-40">Customer Segment:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.customerSegment }}</span>
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.currency.name }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.currency.decimalPlaces }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Currency in multiples of' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.currency.inMultiplesOf }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Terms' | translate }}</h3>
|
|
||||||
|
|
||||||
<mat-divider [inset]="true"></mat-divider>
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.nominalAnnualInterestRate | formatNumber }} %</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.interestCompoundingPeriodType.value | translateKey: 'catalogs' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.interestPostingPeriodType.value | translateKey: 'catalogs' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Interest Calculated using' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.interestCalculationType.value | translateKey: 'catalogs' }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{
|
|
||||||
savingProduct.interestCalculationDaysInYearType.value | translateKey: 'catalogs'
|
|
||||||
}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Settings' | translate }}</h3>
|
|
||||||
|
|
||||||
<mat-divider [inset]="true"></mat-divider>
|
|
||||||
|
|
||||||
@if (savingProduct.minRequiredOpeningBalance) {
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Minimum Opening Balance' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.minRequiredOpeningBalance | formatNumber }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (savingProduct.lockinPeriodFrequency) {
|
@if (savingProduct.productCode) {
|
||||||
<div class="flex-fill layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40">{{ 'labels.inputs.Lock-in Period' | translate }}:</span>
|
<span class="flex-40">Product Code:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.productCode }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.effectiveDate) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Effective Date:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.effectiveDate }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.effectiveTo) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Effective To:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.effectiveTo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.endDate) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">End Date:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.endDate }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.introducerRequired !== undefined) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Introducer Required:</span>
|
||||||
<span class="flex-60">{{
|
<span class="flex-60">{{
|
||||||
savingProduct.lockinPeriodFrequency + ' ' + savingProduct.lockinPeriodFrequencyType.value
|
savingProduct.introducerRequired === true || savingProduct.introducerRequired === 'Yes' ? 'Yes' : 'No'
|
||||||
}}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
@if (savingProduct.shareLinkingEnabled !== undefined) {
|
||||||
<span class="flex-40">{{ 'labels.inputs.Apply Withdrawal Fee for Transfers' | translate }}:</span>
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-60">{{ savingProduct.withdrawalFeeForTransfers | yesNo }}</span>
|
<span class="flex-40">Share Linking Enabled:</span>
|
||||||
</div>
|
<span class="flex-60">{{ savingProduct.shareLinkingEnabled | yesNo }}</span>
|
||||||
|
|
||||||
@if (savingProduct.minBalanceForInterestCalculation) {
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.minBalanceForInterestCalculation | formatNumber }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||||
<span class="flex-40">{{ 'labels.inputs.Enforce Minimum Balance' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.enforceMinRequiredBalance | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (savingProduct.minRequiredBalance) {
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Minimum Balance' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.minRequiredBalance | formatNumber }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
<div class="flex-100 layout-row">
|
||||||
<span class="flex-40">{{ 'labels.inputs.Withhold Tax is Applicable' | translate }}:</span>
|
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}:</span>
|
||||||
<span class="flex-60">{{ savingProduct.withHoldTax | yesNo }}</span>
|
<span class="flex-60">{{ savingProduct.currency.name }}</span>
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (savingProduct.withHoldTax) {
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Tax Group' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.taxGroup.name }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Is Overdraft Allowed' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.allowOverdraft | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (savingProduct.minOverdraftForInterestCalculation) {
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40"
|
|
||||||
>{{ 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate }}:</span
|
|
||||||
>
|
|
||||||
<span class="flex-60">{{ savingProduct.minOverdraftForInterestCalculation }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (savingProduct.nominalAnnualInterestRateOverdraft) {
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.nominalAnnualInterestRateOverdraft }} %</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (savingProduct.overdraftLimit) {
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.overdraftLimit | formatNumber }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="flex-fill layout-row">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Enable Dormancy Tracking' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.isDormancyTrackingActive | yesNo }}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (savingProduct.isDormancyTrackingActive) {
|
|
||||||
<div class="layout-row-wrap responsive-column flex-fill">
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Number of Days to Inactive sub-status' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.daysToInactive }}</span>
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Number of Days to Dormant sub-status' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.daysToDormancy }}</span>
|
|
||||||
<span class="flex-40">{{ 'labels.inputs.Number of Days to Escheat' | translate }}:</span>
|
|
||||||
<span class="flex-60">{{ savingProduct.daysToEscheat }}</span>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (savingProduct.charges.length) {
|
|
||||||
<div class="layout-row-wrap responsive-column flex-fill">
|
|
||||||
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Charges' | translate }}</h3>
|
|
||||||
<mat-divider [inset]="true"></mat-divider>
|
|
||||||
<table class="flex-fill mat-elevation-z1" mat-table [dataSource]="savingProduct.charges">
|
|
||||||
<ng-container matColumnDef="name">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="chargeCalculationType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
{{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="amount">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
{{ charge.amount | formatNumber }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="chargeTimeType">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
|
||||||
<td mat-cell *matCellDef="let charge">
|
|
||||||
{{ charge.chargeTimeType.value | translateKey: 'catalogs' }}
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
|
||||||
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Accounting' | translate }}</h3>
|
|
||||||
|
|
||||||
<mat-divider [inset]="true"></mat-divider>
|
|
||||||
|
|
||||||
<mifosx-view-savings-accounting-details
|
|
||||||
class="flex-100"
|
|
||||||
[accountingRule]="savingProduct.accountingRule"
|
|
||||||
[accountingMappings]="savingProduct.accountingMappings"
|
|
||||||
>
|
|
||||||
</mifosx-view-savings-accounting-details>
|
|
||||||
|
|
||||||
@if (
|
|
||||||
savingProduct.paymentChannelToFundSourceMappings?.length ||
|
|
||||||
savingProduct.feeToIncomeAccountMappings?.length ||
|
|
||||||
savingProduct.penaltyToIncomeAccountMappings?.length
|
|
||||||
) {
|
|
||||||
<div class="flex-fill layout-row-wrap responsive-column">
|
|
||||||
<h3 class="mat-h3 flex-fill">{{ 'labels.inputs.Advanced Accounting Rules' | translate }}</h3>
|
|
||||||
<mat-divider [inset]="true"></mat-divider>
|
|
||||||
@if (savingProduct.paymentChannelToFundSourceMappings?.length) {
|
|
||||||
<div class="flex-fill layout-row-wrap responsive-column">
|
|
||||||
<h4 class="mat-h4 flex-fill">{{ 'labels.inputs.Fund Sources for Payment Channels' | translate }}</h4>
|
|
||||||
<table
|
|
||||||
class="mat-elevation-z1 flex-fill"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="savingProduct.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.glCode }}) {{ 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 (savingProduct.feeToIncomeAccountMappings?.length) {
|
|
||||||
<div class="flex-fill layout-row-wrap responsive-column">
|
|
||||||
<h4 class="mat-h4 flex-fill">{{ 'labels.inputs.Fees to Specific Income Accounts' | translate }}</h4>
|
|
||||||
<table class="flex-fill mat-elevation-z1" mat-table [dataSource]="savingProduct.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.glCode }}) {{ 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 (savingProduct.penaltyToIncomeAccountMappings?.length) {
|
|
||||||
<div class="flex-fill layout-row-wrap responsive-column">
|
|
||||||
<h4 class="mat-h4 flex-fill">{{ 'labels.inputs.Penalties to Specific Income Accounts' | translate }}</h4>
|
|
||||||
<table
|
|
||||||
class="mat-elevation-z1 flex-fill"
|
|
||||||
mat-table
|
|
||||||
[dataSource]="savingProduct.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.glCode }}) {{ 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>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.currency.decimalPlaces }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.currency.inMultiplesOf !== undefined && savingProduct.currency.inMultiplesOf !== null) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Currency in multiples of' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.currency.inMultiplesOf }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.dormancyAlert !== undefined) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Dormancy Alert:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.dormancyAlert | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.nominationFacility !== undefined) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Nomination Facility:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.nominationFacility | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Terms' | translate }}</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.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">{{ savingProduct.interestCompoundingPeriodType.value | translateKey: 'catalogs' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.interestPostingPeriodType.value | translateKey: 'catalogs' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Interest Calculated using' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.interestCalculationType.value | translateKey: 'catalogs' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Days in Year' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.interestCalculationDaysInYearType.value | translateKey: 'catalogs' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.effectiveDate) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Effective Date:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.effectiveDate }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Interest Calculate:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.interestCalculateSelector }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.averagePeriod) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Average Period:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.averagePeriod }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Auto Post:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.autoPost | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.frequencyFixDay) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Frequency Fix Day:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.frequencyFixDay }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.lastInterestDate) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Last Interest Date:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.lastInterestDate }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Settings' | translate }}</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
|
||||||
|
@if (savingProduct.minRequiredOpeningBalance) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Minimum Opening Balance' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.minRequiredOpeningBalance | formatNumber }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.lockinPeriodFrequency) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Lock-in Period' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{
|
||||||
|
savingProduct.lockinPeriodFrequency + ' ' + savingProduct.lockinPeriodFrequencyType.value
|
||||||
|
}}</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">{{ savingProduct.withdrawalFeeForTransfers | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.balanceFrequency) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Balance Frequency:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.balanceFrequency }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Enable Lock-in Period:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.enableLockinPeriod | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.minBalanceForInterestCalculation) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.minBalanceForInterestCalculation | formatNumber }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Enforce Minimum Balance' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.enforceMinRequiredBalance | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.minRequiredBalance) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Minimum Balance' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.minRequiredBalance | 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">{{ savingProduct.withHoldTax | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.withHoldTax) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Tax Group' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.taxGroup.name }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Is Overdraft Allowed' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.allowOverdraft | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Account Closed Zero Balance in Day Closing:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.accountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.minOverdraftForInterestCalculation) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40"
|
||||||
|
>{{ 'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate }}:</span
|
||||||
|
>
|
||||||
|
<span class="flex-60">{{ savingProduct.minOverdraftForInterestCalculation | formatNumber }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.nominalAnnualInterestRateOverdraft) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.nominalAnnualInterestRateOverdraft | formatNumber }} %</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.overdraftLimit) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.overdraftLimit | formatNumber }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Enable Dormancy Tracking' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.isDormancyTrackingActive | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (savingProduct.isDormancyTrackingActive) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Number of Days to Inactive sub-status' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.daysToInactive }}</span>
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Number of Days to Dormant sub-status' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.daysToDormancy }}</span>
|
||||||
|
<span class="flex-40">{{ 'labels.inputs.Number of Days to Escheat' | translate }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.daysToEscheat }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3 class="mat-h3 flex-100">Account Facilities</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
@if (savingProduct.accountFacilityConfig) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
|
@for (feature of accountFacilityFeatureItems; track feature.key) {
|
||||||
|
@if (savingProduct.accountFacilityConfig[feature.key] !== undefined) {
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">{{ feature.label }}:</span>
|
||||||
|
<span class="flex-60">{{ savingProduct.accountFacilityConfig[feature.key] | yesNo }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.accountFacilityLimits?.length) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
|
<h4 class="mat-h4 flex-100">Account Facility Limits</h4>
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="savingProduct.accountFacilityLimits">
|
||||||
|
<ng-container matColumnDef="limitType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Limit Type</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.limitType }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="generalLimit">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>General Limit</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.generalLimit }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="minorAccountLimit">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Minor Account Limit</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.minorAccountLimit }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="smallSavingLimit">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>Small Saving Limit</th>
|
||||||
|
<td mat-cell *matCellDef="let row">{{ row.smallSavingLimit }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr
|
||||||
|
mat-header-row
|
||||||
|
*matHeaderRowDef="['limitType', 'generalLimit', 'minorAccountLimit', 'smallSavingLimit']"
|
||||||
|
></tr>
|
||||||
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: ['limitType', 'generalLimit', 'minorAccountLimit', 'smallSavingLimit']"
|
||||||
|
></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.depositLimitKyc) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
|
<h4 class="mat-h4 flex-100">Deposit Limit (KYC)</h4>
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">General:</span>
|
||||||
|
<span class="flex-60"
|
||||||
|
>{{ savingProduct.depositLimitKyc.general.kycType }} - {{ savingProduct.depositLimitKyc.general.limit }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Minor:</span>
|
||||||
|
<span class="flex-60"
|
||||||
|
>{{ savingProduct.depositLimitKyc.minor.kycType }} - {{ savingProduct.depositLimitKyc.minor.limit }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex-100 layout-row">
|
||||||
|
<span class="flex-40">Small Saving:</span>
|
||||||
|
<span class="flex-60"
|
||||||
|
>{{ savingProduct.depositLimitKyc.smallSaving.kycType }} -
|
||||||
|
{{ savingProduct.depositLimitKyc.smallSaving.limit }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (savingProduct.charges.length) {
|
||||||
|
<div class="layout-row-wrap responsive-column flex-100">
|
||||||
|
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Charges' | translate }}</h3>
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="savingProduct.charges">
|
||||||
|
<ng-container matColumnDef="name">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Name' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.name + ', ' + charge.currency.displaySymbol }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="chargeCalculationType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Type' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.chargeCalculationType.value | translateKey: 'catalogs' }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="amount">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.amount | formatNumber }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="chargeTimeType">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let charge">
|
||||||
|
{{ charge.chargeTimeType.value | translateKey: 'catalogs' }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="chargesDisplayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: chargesDisplayedColumns"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Accounting' | translate }}</h3>
|
||||||
|
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
|
||||||
|
<mifosx-view-savings-accounting-details
|
||||||
|
class="flex-100"
|
||||||
|
[accountingRule]="savingProduct.accountingRule"
|
||||||
|
[accountingMappings]="savingProduct.accountingMappings"
|
||||||
|
>
|
||||||
|
</mifosx-view-savings-accounting-details>
|
||||||
|
|
||||||
|
@if (
|
||||||
|
savingProduct.paymentChannelToFundSourceMappings?.length ||
|
||||||
|
savingProduct.feeToIncomeAccountMappings?.length ||
|
||||||
|
savingProduct.penaltyToIncomeAccountMappings?.length
|
||||||
|
) {
|
||||||
|
<div class="flex-100 layout-row-wrap responsive-column">
|
||||||
|
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Advanced Accounting Rules' | translate }}</h3>
|
||||||
|
<mat-divider class="flex-100"></mat-divider>
|
||||||
|
@if (savingProduct.paymentChannelToFundSourceMappings?.length) {
|
||||||
|
<div class="flex-100 layout-row-wrap responsive-column">
|
||||||
|
<h4 class="mat-h4 flex-100">{{ 'labels.inputs.Fund Sources for Payment Channels' | translate }}</h4>
|
||||||
|
<table
|
||||||
|
class="mat-elevation-z1 flex-100"
|
||||||
|
mat-table
|
||||||
|
[dataSource]="savingProduct.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.glCode }}) {{ 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 (savingProduct.feeToIncomeAccountMappings?.length) {
|
||||||
|
<div class="flex-100 layout-row-wrap responsive-column">
|
||||||
|
<h4 class="mat-h4 flex-100">{{ 'labels.inputs.Fees to Specific Income Accounts' | translate }}</h4>
|
||||||
|
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="savingProduct.feeToIncomeAccountMappings">
|
||||||
|
<ng-container matColumnDef="chargeId">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fee/Charge' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let fee">
|
||||||
|
{{ fee.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 fee">({{ fee.incomeAccount.glCode }}) {{ fee.incomeAccount.name }}</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@if (savingProduct.penaltyToIncomeAccountMappings?.length) {
|
||||||
|
<div class="flex-100 layout-row-wrap responsive-column">
|
||||||
|
<h4 class="mat-h4 flex-100">{{ 'labels.inputs.Penalties to Specific Income Accounts' | translate }}</h4>
|
||||||
|
<table
|
||||||
|
class="flex-100 mat-elevation-z1"
|
||||||
|
mat-table
|
||||||
|
[dataSource]="savingProduct.penaltyToIncomeAccountMappings"
|
||||||
|
>
|
||||||
|
<ng-container matColumnDef="chargeId">
|
||||||
|
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
|
||||||
|
<td mat-cell *matCellDef="let penalty">
|
||||||
|
{{ penalty.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 penalty">
|
||||||
|
({{ penalty.incomeAccount.glCode }}) {{ penalty.incomeAccount.name }}
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+92
@@ -71,3 +71,95 @@ mat-divider {
|
|||||||
.margin-t {
|
.margin-t {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spa-gl-summary,
|
||||||
|
.spa-other-gl-summary {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.9rem;
|
||||||
|
margin-top: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-gl-section h5 {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #91a0bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-table:first-of-type {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-summary .mat-elevation-z1 {
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-table th.mat-mdc-header-cell,
|
||||||
|
.spa-other-gl-table td.mat-mdc-cell {
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
padding-right: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-table th.mat-mdc-header-cell:first-child,
|
||||||
|
.spa-other-gl-table td.mat-mdc-cell:first-child {
|
||||||
|
width: 38%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spa-other-gl-table th.mat-mdc-header-cell:last-child,
|
||||||
|
.spa-other-gl-table td.mat-mdc-cell:last-child {
|
||||||
|
width: 62%;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width <= 768px) {
|
||||||
|
.spa-gl-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+37
@@ -72,6 +72,25 @@ export class SavingProductGeneralTabComponent {
|
|||||||
'incomeAccountId'
|
'incomeAccountId'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
accountFacilityFeatureItems = [
|
||||||
|
{ key: 'atmFacility', label: 'ATM Facility' },
|
||||||
|
{ key: 'atmWithdrawalFacility', label: 'ATM Withdrawal Facility' },
|
||||||
|
{ key: 'upiFacility', label: 'UPI Facility' },
|
||||||
|
{ key: 'posTransactionsFacility', label: 'POS Transactions Facility' },
|
||||||
|
{ key: 'chequeBook', label: 'Cheque Book' },
|
||||||
|
{ key: 'impsFacility', label: 'IMPS Facility' },
|
||||||
|
{ key: 'smsAlerts', label: 'SMS Alerts' },
|
||||||
|
{ key: 'bbpsFacility', label: 'BBPS Facility' },
|
||||||
|
{ key: 'insuranceCover', label: 'Insurance Cover' },
|
||||||
|
{ key: 'bepgFacility', label: 'BEPG Facility' },
|
||||||
|
{ key: 'neftFacility', label: 'NEFT Facility' },
|
||||||
|
{ key: 'rtgsFacility', label: 'RTGS Facility' },
|
||||||
|
{ key: 'interBankTotalCapFacility', label: 'Inter-Bank Total Cap Facility' },
|
||||||
|
{ key: 'withdrawalFacility', label: 'Withdrawal Facility' },
|
||||||
|
{ key: 'depositFacilityKyc', label: 'Deposit Facility (KYC)' },
|
||||||
|
{ key: 'virtualDebitCard', label: 'Virtual Debit Card' }
|
||||||
|
];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.route.data.subscribe((data: { savingProduct: any }) => {
|
this.route.data.subscribe((data: { savingProduct: any }) => {
|
||||||
this.savingProduct = data.savingProduct;
|
this.savingProduct = data.savingProduct;
|
||||||
@@ -85,4 +104,22 @@ export class SavingProductGeneralTabComponent {
|
|||||||
isAccrualAccounting(): boolean {
|
isAccrualAccounting(): boolean {
|
||||||
return this.accounting.isAccrualAccounting(this.savingProduct.accountingRule);
|
return this.accounting.isAccrualAccounting(this.savingProduct.accountingRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = options.find((option) => option?.glCode === glCode);
|
||||||
|
return account ? `(${account.glCode}) ${account.name}` : glCode;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,12 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN!
|
// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN!
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
export default {
|
export default {
|
||||||
'mifos_x': {
|
'mifos_x': {
|
||||||
'version': '260407',
|
'version': '260413',
|
||||||
'hash': 'gb890d24bb'
|
'hash': '79e455c4'
|
||||||
},
|
},
|
||||||
'allow_switching_backend_instance': true
|
'allow_switching_backend_instance': true
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user