update current product code and RD code
This commit is contained in:
+5
-1
@@ -6,7 +6,11 @@
|
||||
"Bash(git commit *)",
|
||||
"Bash(select-object *)",
|
||||
"Bash(git config *)",
|
||||
"Bash(set *)"
|
||||
"Bash(set *)",
|
||||
"Bash(git push)",
|
||||
"Bash(xcopy *)",
|
||||
"Bash(powershell *)",
|
||||
"mcp__idea__build_project"
|
||||
]
|
||||
},
|
||||
"$version": 3
|
||||
|
||||
@@ -98,10 +98,10 @@
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.cc-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -98,10 +98,10 @@
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.ec-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -109,10 +109,10 @@
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.vc-top-bar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -72,10 +72,10 @@
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.ccl-section__body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -72,10 +72,10 @@
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.ecl-section__body {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.vcl-top-bar {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
+44
-25
@@ -12,14 +12,21 @@
|
||||
<h2 class="cp-header__title">{{ 'labels.heading.Create Current Product' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<mat-tab-group class="cp-tabs" animationDuration="200ms" [preserveContent]="true" [(selectedIndex)]="selectedTabIndex">
|
||||
<mat-tab-group
|
||||
class="cp-tabs"
|
||||
animationDuration="200ms"
|
||||
[preserveContent]="true"
|
||||
[(selectedIndex)]="selectedTabIndex"
|
||||
>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-info-circle cp-tab-icon"></i>
|
||||
{{ 'labels.inputs.DETAILS' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-saving-product-details-step></mifosx-saving-product-details-step>
|
||||
<mifosx-current-product-details-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
></mifosx-current-product-details-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -29,9 +36,9 @@
|
||||
{{ 'labels.inputs.CURRENCY' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-saving-product-currency-step
|
||||
[savingProductsTemplate]="currentProductsTemplate"
|
||||
></mifosx-saving-product-currency-step>
|
||||
<mifosx-current-product-currency-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
></mifosx-current-product-currency-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -41,9 +48,9 @@
|
||||
{{ 'labels.inputs.TERMS' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-saving-product-terms-step
|
||||
[savingProductsTemplate]="currentProductsTemplate"
|
||||
></mifosx-saving-product-terms-step>
|
||||
<mifosx-current-product-terms-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
></mifosx-current-product-terms-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -53,9 +60,9 @@
|
||||
{{ 'labels.inputs.SETTINGS' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-saving-product-settings-step
|
||||
[savingProductsTemplate]="currentProductsTemplate"
|
||||
></mifosx-saving-product-settings-step>
|
||||
<mifosx-current-product-settings-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
></mifosx-current-product-settings-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -65,10 +72,22 @@
|
||||
{{ 'labels.inputs.CHARGES' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-saving-product-charges-step
|
||||
[savingProductsTemplate]="currentProductsTemplate"
|
||||
<mifosx-current-product-charges-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
[currencyCode]="currencyForm.get('currencyCode')"
|
||||
></mifosx-saving-product-charges-step>
|
||||
></mifosx-current-product-charges-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-mobile-alt cp-tab-icon"></i>
|
||||
Digital
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-limits-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
></mifosx-current-product-limits-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -78,13 +97,13 @@
|
||||
{{ 'labels.inputs.ACCOUNTING' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-saving-product-accounting-step
|
||||
[savingProductsTemplate]="currentProductsTemplate"
|
||||
<mifosx-current-product-accounting-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[isDormancyTrackingActive]="settingsForm.get('isDormancyTrackingActive')"
|
||||
[savingProductFormValid]="formValid"
|
||||
[CurrentProductFormValid]="formValid"
|
||||
[allowOverdraft]="settingsForm.get('allowOverdraft')"
|
||||
></mifosx-saving-product-accounting-step>
|
||||
></mifosx-current-product-accounting-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@@ -95,13 +114,13 @@
|
||||
{{ 'labels.inputs.PREVIEW' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-saving-product-preview-step
|
||||
[savingProductsTemplate]="currentProductsTemplate"
|
||||
<mifosx-current-product-preview-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[savingProduct]="currentProduct"
|
||||
[CurrentProduct]="currentProduct"
|
||||
[taskPermission]="'CREATE_CURRENTPRODUCT'"
|
||||
(submitEvent)="submit()"
|
||||
></mifosx-saving-product-preview-step>
|
||||
></mifosx-current-product-preview-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
}
|
||||
@@ -114,19 +133,19 @@
|
||||
<div class="cp-action-bar__right">
|
||||
@if (selectedTabIndex > 0) {
|
||||
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
||||
<i class="fas fa-arrow-left" style="margin-right: 8px;"></i>
|
||||
<i class="fas fa-arrow-left" style="margin-right: 8px"></i>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (selectedTabIndex < totalTabs - 1) {
|
||||
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<i class="fas fa-arrow-right" style="margin-left: 8px;"></i>
|
||||
<i class="fas fa-arrow-right" style="margin-left: 8px"></i>
|
||||
</button>
|
||||
}
|
||||
@if (formValid && selectedTabIndex === totalTabs - 1) {
|
||||
<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 }}
|
||||
</button>
|
||||
}
|
||||
|
||||
+4
-4
@@ -35,11 +35,11 @@ $primary: #1353a0;
|
||||
.cp-tabs {
|
||||
background: var(--md-sys-color-surface, #fff);
|
||||
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 {
|
||||
.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 {
|
||||
@@ -77,10 +77,10 @@ $primary: #1353a0;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
|
||||
&__cancel {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
color: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
&__right {
|
||||
|
||||
+71
-38
@@ -8,13 +8,14 @@
|
||||
|
||||
import { Component, ViewChild, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { SavingProductDetailsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component';
|
||||
import { SavingProductCurrencyStepComponent } from '../../saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component';
|
||||
import { SavingProductTermsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component';
|
||||
import { SavingProductSettingsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component';
|
||||
import { SavingProductChargesStepComponent } from '../../saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component';
|
||||
import { SavingProductAccountingStepComponent } from '../../saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component';
|
||||
import { SavingProductPreviewStepComponent } from '../../saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component';
|
||||
import { CurrentProductDetailsStepComponent } from '../current-product-stepper/current-product-details-step/current-product-details-step.component';
|
||||
import { CurrentProductCurrencyStepComponent } from '../current-product-stepper/current-product-currency-step/current-product-currency-step.component';
|
||||
import { CurrentProductTermsStepComponent } from '../current-product-stepper/current-product-terms-step/current-product-terms-step.component';
|
||||
import { CurrentProductSettingsStepComponent } from '../current-product-stepper/current-product-settings-step/current-product-settings-step.component';
|
||||
import { CurrentProductChargesStepComponent } from '../current-product-stepper/current-product-charges-step/current-product-charges-step.component';
|
||||
import { CurrentProductLimitsStepComponent } from '../current-product-stepper/current-product-limits-step/current-product-limits-step.component';
|
||||
import { CurrentProductAccountingStepComponent } from '../current-product-stepper/current-product-accounting-step/current-product-accounting-step.component';
|
||||
import { CurrentProductPreviewStepComponent } from '../current-product-stepper/current-product-preview-step/current-product-preview-step.component';
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
@@ -28,13 +29,14 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTabsModule,
|
||||
SavingProductDetailsStepComponent,
|
||||
SavingProductCurrencyStepComponent,
|
||||
SavingProductTermsStepComponent,
|
||||
SavingProductSettingsStepComponent,
|
||||
SavingProductChargesStepComponent,
|
||||
SavingProductAccountingStepComponent,
|
||||
SavingProductPreviewStepComponent
|
||||
CurrentProductDetailsStepComponent,
|
||||
CurrentProductCurrencyStepComponent,
|
||||
CurrentProductTermsStepComponent,
|
||||
CurrentProductSettingsStepComponent,
|
||||
CurrentProductChargesStepComponent,
|
||||
CurrentProductLimitsStepComponent,
|
||||
CurrentProductAccountingStepComponent,
|
||||
CurrentProductPreviewStepComponent
|
||||
]
|
||||
})
|
||||
export class CreateCurrentProductComponent {
|
||||
@@ -44,13 +46,14 @@ export class CreateCurrentProductComponent {
|
||||
private settingsService = inject(SettingsService);
|
||||
private accounting = inject(Accounting);
|
||||
|
||||
@ViewChild(SavingProductDetailsStepComponent, { static: true }) detailsStep: SavingProductDetailsStepComponent;
|
||||
@ViewChild(SavingProductCurrencyStepComponent, { static: true }) currencyStep: SavingProductCurrencyStepComponent;
|
||||
@ViewChild(SavingProductTermsStepComponent, { static: true }) termsStep: SavingProductTermsStepComponent;
|
||||
@ViewChild(SavingProductSettingsStepComponent, { static: true }) settingsStep: SavingProductSettingsStepComponent;
|
||||
@ViewChild(SavingProductChargesStepComponent, { static: true }) chargesStep: SavingProductChargesStepComponent;
|
||||
@ViewChild(SavingProductAccountingStepComponent, { static: true })
|
||||
accountingStep: SavingProductAccountingStepComponent;
|
||||
@ViewChild(CurrentProductDetailsStepComponent, { static: true }) detailsStep: CurrentProductDetailsStepComponent;
|
||||
@ViewChild(CurrentProductCurrencyStepComponent, { static: true }) currencyStep: CurrentProductCurrencyStepComponent;
|
||||
@ViewChild(CurrentProductTermsStepComponent, { static: true }) termsStep: CurrentProductTermsStepComponent;
|
||||
@ViewChild(CurrentProductSettingsStepComponent, { static: true }) settingsStep: CurrentProductSettingsStepComponent;
|
||||
@ViewChild(CurrentProductChargesStepComponent, { static: true }) chargesStep: CurrentProductChargesStepComponent;
|
||||
@ViewChild(CurrentProductLimitsStepComponent, { static: true }) limitsStep: CurrentProductLimitsStepComponent;
|
||||
@ViewChild(CurrentProductAccountingStepComponent, { static: true })
|
||||
accountingStep: CurrentProductAccountingStepComponent;
|
||||
|
||||
currentProductsTemplate: any;
|
||||
accountingRuleData: string[] = [];
|
||||
@@ -64,23 +67,26 @@ export class CreateCurrentProductComponent {
|
||||
}
|
||||
|
||||
get detailsForm() {
|
||||
return this.detailsStep.savingProductDetailsForm;
|
||||
return this.detailsStep.CurrentProductDetailsForm;
|
||||
}
|
||||
get currencyForm() {
|
||||
return this.currencyStep.savingProductCurrencyForm;
|
||||
return this.currencyStep.CurrentProductCurrencyForm;
|
||||
}
|
||||
get termsForm() {
|
||||
return this.termsStep.savingProductTermsForm;
|
||||
return this.termsStep.CurrentProductTermsForm;
|
||||
}
|
||||
get settingsForm() {
|
||||
return this.settingsStep.savingProductSettingsForm;
|
||||
return this.settingsStep.CurrentProductSettingsForm;
|
||||
}
|
||||
get limitsForm(): void {
|
||||
return; // Limits step doesn't have a form, it's data-driven
|
||||
}
|
||||
get accountingForm() {
|
||||
return this.accountingStep.savingProductAccountingForm;
|
||||
return this.accountingStep.CurrentProductAccountingForm;
|
||||
}
|
||||
|
||||
get totalTabs(): number {
|
||||
return this.formValid ? 7 : 6;
|
||||
return this.formValid ? 8 : 7;
|
||||
}
|
||||
|
||||
get formValid() {
|
||||
@@ -95,23 +101,50 @@ export class CreateCurrentProductComponent {
|
||||
|
||||
get currentProduct() {
|
||||
return {
|
||||
...this.detailsStep.savingProductDetails,
|
||||
...this.currencyStep.savingProductCurrency,
|
||||
...this.termsStep.savingProductTerms,
|
||||
...this.settingsStep.savingProductSettings,
|
||||
...this.chargesStep.savingProductCharges,
|
||||
...this.accountingStep.savingProductAccounting
|
||||
...this.detailsStep.CurrentProductDetails,
|
||||
...this.currencyStep.CurrentProductCurrency,
|
||||
...this.termsStep.CurrentProductTerms,
|
||||
...this.settingsStep.CurrentProductSettings,
|
||||
...this.chargesStep.CurrentProductCharges,
|
||||
...this.limitsStep.CurrentProductLimits,
|
||||
...this.accountingStep.CurrentProductAccounting
|
||||
};
|
||||
}
|
||||
|
||||
submit() {
|
||||
const product = {
|
||||
private prepareCurrentProductPayload() {
|
||||
const currentProduct = {
|
||||
...this.currentProduct,
|
||||
charges: this.currentProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code
|
||||
locale: this.settingsService.language.code,
|
||||
dateFormat: 'dd MMMM yyyy'
|
||||
};
|
||||
delete product.advancedAccountingRules;
|
||||
this.productsService.createCurrentProduct(product).subscribe((response: any) => {
|
||||
delete currentProduct.advancedAccountingRules;
|
||||
|
||||
// Convert lockinPeriodFrequencyType from string to integer
|
||||
// Backend expects: 0=DAYS, 1=WEEKS, 2=MONTHS, 3=YEARS
|
||||
if (currentProduct.lockinPeriodFrequencyType) {
|
||||
const frequencyTypeMapping: { [key: string]: number } = {
|
||||
DAYS: 0,
|
||||
WEEKS: 1,
|
||||
MONTHS: 2,
|
||||
YEARS: 3
|
||||
};
|
||||
currentProduct.lockinPeriodFrequencyType = frequencyTypeMapping[currentProduct.lockinPeriodFrequencyType];
|
||||
}
|
||||
|
||||
Object.keys(currentProduct).forEach((key) => {
|
||||
if (currentProduct[key] === '' || currentProduct[key] === null || currentProduct[key] === undefined) {
|
||||
delete currentProduct[key];
|
||||
}
|
||||
});
|
||||
|
||||
return currentProduct;
|
||||
}
|
||||
|
||||
submit() {
|
||||
const currentProduct = this.prepareCurrentProductPayload();
|
||||
console.log('Current product submit payload:', currentProduct);
|
||||
this.productsService.createCurrentProduct(currentProduct).subscribe((response: any) => {
|
||||
this.router.navigate(
|
||||
[
|
||||
'../',
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<form [formGroup]="CurrentProductAccountingForm">
|
||||
<div class="spa-section">
|
||||
<div class="spa-section__header">
|
||||
<span class="spa-section__icon">
|
||||
<i class="fas fa-book"></i>
|
||||
</span>
|
||||
<h3>Accounting</h3>
|
||||
</div>
|
||||
|
||||
<div class="spa-section__body">
|
||||
<div class="spa-group">
|
||||
<div class="spa-group__header">
|
||||
<h4>Accounting Rule</h4>
|
||||
</div>
|
||||
<div class="spa-radio-group">
|
||||
<mat-radio-button [value]="2" [checked]="true" [disabled]="true">Cash</mat-radio-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isCashOrAccrualAccounting()) {
|
||||
@for (section of accountingSectionConfigs; track section.key) {
|
||||
<div class="spa-group">
|
||||
<div class="spa-group__header">
|
||||
<h4>{{ section.title }}</h4>
|
||||
<button type="button" class="spa-add-btn" (click)="addOtherGlMapping(section.otherMappingsControlName)">
|
||||
+ Add Other
|
||||
</button>
|
||||
</div>
|
||||
<div class="spa-list">
|
||||
@for (row of section.rows; track row.key) {
|
||||
@if (
|
||||
(!row.accrualOnly || isAccrualAccounting()) && (!row.dormancyOnly || isDormancyTrackingEnabled())
|
||||
) {
|
||||
<div class="spa-row">
|
||||
<div class="spa-row__meta">
|
||||
<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>
|
||||
<span class="spa-row__label">{{ row.label }}</span>
|
||||
</div>
|
||||
<mifosx-gl-account-selector
|
||||
class="spa-row__input"
|
||||
[inputFormControl]="CurrentProductAccountingForm.get(row.controlName)"
|
||||
[glAccountList]="row.options"
|
||||
[required]="row.required"
|
||||
[inputLabel]="row.inputLabel"
|
||||
></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>
|
||||
</div>
|
||||
</form>
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-500: #6b7280;
|
||||
$gray-700: #334155;
|
||||
$gray-900: #202124;
|
||||
$primary: #1353a0;
|
||||
|
||||
.spa-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.spa-section__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.spa-section__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.spa-section__body {
|
||||
padding: 16px 24px 20px;
|
||||
}
|
||||
|
||||
.spa-group {
|
||||
& + & {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.spa-add-btn {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #2563eb;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.spa-list {
|
||||
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;
|
||||
background: #fff;
|
||||
|
||||
@media (width <= 900px) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
}
|
||||
+495
@@ -0,0 +1,495 @@
|
||||
/**
|
||||
* 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, OnInit, inject } from '@angular/core';
|
||||
import {
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
UntypedFormArray,
|
||||
UntypedFormBuilder,
|
||||
UntypedFormControl,
|
||||
UntypedFormGroup,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
|
||||
import { GlAccountSelectorComponent } from 'app/shared/accounting/gl-account-selector/gl-account-selector.component';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { GLAccount } from 'app/shared/models/general.model';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income';
|
||||
|
||||
type AccountingRowConfig = {
|
||||
key: string;
|
||||
label: string;
|
||||
inputLabel: string;
|
||||
controlName: string;
|
||||
options: GLAccount[];
|
||||
required?: boolean;
|
||||
accrualOnly?: boolean;
|
||||
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({
|
||||
selector: 'mifosx-current-product-accounting-step',
|
||||
templateUrl: './current-product-accounting-step.component.html',
|
||||
styleUrls: ['./current-product-accounting-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatRadioGroup,
|
||||
MatRadioButton,
|
||||
GlAccountSelectorComponent
|
||||
]
|
||||
})
|
||||
export class CurrentProductAccountingStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private accounting = inject(Accounting);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
@Input() accountingRuleData: any;
|
||||
@Input() isDormancyTrackingActive: any;
|
||||
@Input() CurrentProductFormValid: boolean;
|
||||
@Input() allowOverdraft: any;
|
||||
|
||||
CurrentProductAccountingForm: UntypedFormGroup;
|
||||
liabilityAccountOptions: GLAccount[] = [];
|
||||
expenseAccountOptions: GLAccount[] = [];
|
||||
incomeAccountOptions: GLAccount[] = [];
|
||||
assetAccountOptions: GLAccount[] = [];
|
||||
accountingSectionConfigs: AccountingSectionConfig[] = [];
|
||||
|
||||
constructor() {
|
||||
this.createCurrentProductAccountingForm();
|
||||
this.setConditionalControls();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const accountingMappings = this.CurrentProductsTemplate?.accountingMappings || {};
|
||||
// otherGlMappingsMap is at the root of the template, not inside accountingMappings
|
||||
const mappings = {
|
||||
...accountingMappings,
|
||||
otherGlMappingsMap: this.CurrentProductsTemplate?.otherGlMappingsMap
|
||||
};
|
||||
const accountingMappingOptions = this.CurrentProductsTemplate?.accountingMappingOptions || {};
|
||||
const accountingRuleId = 2; // Always Cash
|
||||
this.assetAccountOptions = accountingMappingOptions.assetAccountOptions || [];
|
||||
this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || [];
|
||||
this.expenseAccountOptions = accountingMappingOptions.expenseAccountOptions || [];
|
||||
this.incomeAccountOptions = accountingMappingOptions.incomeAccountOptions || [];
|
||||
this.buildAccountingSectionConfigs();
|
||||
this.CurrentProductAccountingForm.patchValue({
|
||||
accountingRule: accountingRuleId
|
||||
});
|
||||
|
||||
this.syncAccountingControls(accountingRuleId);
|
||||
this.patchAccountingValues(mappings);
|
||||
this.setOverdraftControl();
|
||||
this.setDormancyControl();
|
||||
}
|
||||
|
||||
createCurrentProductAccountingForm() {
|
||||
this.CurrentProductAccountingForm = this.formBuilder.group({
|
||||
accountingRule: [2],
|
||||
assetsOtherGlMappings: this.formBuilder.array([]),
|
||||
liabilitiesOtherGlMappings: this.formBuilder.array([]),
|
||||
expensesOtherGlMappings: this.formBuilder.array([]),
|
||||
incomeOtherGlMappings: this.formBuilder.array([])
|
||||
});
|
||||
}
|
||||
|
||||
private setConditionalControls() {
|
||||
this.CurrentProductAccountingForm.get('accountingRule')?.valueChanges.subscribe((accountingRule: number) => {
|
||||
this.syncAccountingControls(accountingRule);
|
||||
});
|
||||
}
|
||||
|
||||
private patchAccountingValues(mappings: any) {
|
||||
this.CurrentProductAccountingForm.patchValue({
|
||||
savingsReferenceAccountId: mappings.savingsReferenceAccount?.id || '',
|
||||
savingsControlAccountId: mappings.savingsControlAccount?.id || '',
|
||||
transfersInSuspenseAccountId: mappings.transfersInSuspenseAccount?.id || '',
|
||||
interestOnSavingsAccountId: mappings.interestOnSavingsAccount?.id || '',
|
||||
incomeFromFeeAccountId: mappings.incomeFromFeeAccount?.id || '',
|
||||
incomeFromPenaltyAccountId: mappings.incomeFromPenaltyAccount?.id || '',
|
||||
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 || ''
|
||||
});
|
||||
|
||||
if (this.isCashOrAccrualAccounting()) {
|
||||
// Backend serializes as 'otherGlMappingsMap'; fall back to 'otherGlMappings' for compatibility
|
||||
const glMappings = mappings.otherGlMappingsMap || mappings.otherGlMappings;
|
||||
this.setOtherGlMappings('assetsOtherGlMappings', glMappings?.assets || [], this.assetAccountOptions);
|
||||
this.setOtherGlMappings(
|
||||
'liabilitiesOtherGlMappings',
|
||||
glMappings?.liabilities || [],
|
||||
this.liabilityAccountOptions
|
||||
);
|
||||
this.setOtherGlMappings('expensesOtherGlMappings', glMappings?.expenses || [], this.expenseAccountOptions);
|
||||
this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private createOtherGlMappingGroup(otherGlMapping: Partial<OtherGlMappingValue> = {}) {
|
||||
return this.formBuilder.group({
|
||||
accountName: [
|
||||
otherGlMapping.accountName || '',
|
||||
Validators.required
|
||||
],
|
||||
glAccountId: [
|
||||
otherGlMapping.glAccountId || '',
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
getOtherGlMappingsArray(controlName: string): UntypedFormArray {
|
||||
return this.CurrentProductAccountingForm.get(controlName) as UntypedFormArray;
|
||||
}
|
||||
|
||||
private setOtherGlMappings(controlName: string, otherGlMappings: any[], options: GLAccount[]) {
|
||||
const otherGlMappingsArray = this.getOtherGlMappingsArray(controlName);
|
||||
otherGlMappingsArray.clear();
|
||||
(otherGlMappings || []).forEach((otherGlMapping) => {
|
||||
otherGlMappingsArray.push(
|
||||
this.createOtherGlMappingGroup({
|
||||
accountName: otherGlMapping.accountName,
|
||||
glAccountId:
|
||||
otherGlMapping.glAccountId || options.find((option) => option.glCode === otherGlMapping.glCode)?.id || null
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
addOtherGlMapping(controlName: string) {
|
||||
this.getOtherGlMappingsArray(controlName).push(this.createOtherGlMappingGroup());
|
||||
}
|
||||
|
||||
removeOtherGlMapping(controlName: string, index: number) {
|
||||
this.getOtherGlMappingsArray(controlName).removeAt(index);
|
||||
}
|
||||
|
||||
getOtherGlMappingControl(controlName: string, index: number, fieldName: string): UntypedFormControl {
|
||||
return this.getOtherGlMappingsArray(controlName).at(index).get(fieldName) as UntypedFormControl;
|
||||
}
|
||||
|
||||
private clearOtherGlMappings() {
|
||||
[
|
||||
'assetsOtherGlMappings',
|
||||
'liabilitiesOtherGlMappings',
|
||||
'expensesOtherGlMappings',
|
||||
'incomeOtherGlMappings'
|
||||
].forEach((controlName) => this.getOtherGlMappingsArray(controlName).clear());
|
||||
}
|
||||
|
||||
private setOverdraftControl() {
|
||||
this.allowOverdraft?.valueChanges?.subscribe(() => {
|
||||
this.syncAccountingControls(this.CurrentProductAccountingForm.value.accountingRule);
|
||||
});
|
||||
}
|
||||
|
||||
private setDormancyControl() {
|
||||
this.isDormancyTrackingActive?.valueChanges?.subscribe(() => {
|
||||
this.syncAccountingControls(this.CurrentProductAccountingForm.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.CurrentProductAccountingForm.contains(controlName)) {
|
||||
this.CurrentProductAccountingForm.addControl(
|
||||
controlName,
|
||||
new UntypedFormControl('', required ? Validators.required : undefined)
|
||||
);
|
||||
} else if (required) {
|
||||
this.CurrentProductAccountingForm.get(controlName)?.setValidators(Validators.required);
|
||||
this.CurrentProductAccountingForm.get(controlName)?.updateValueAndValidity({ emitEvent: false });
|
||||
}
|
||||
};
|
||||
|
||||
const removeControl = (controlName: string) => {
|
||||
if (this.CurrentProductAccountingForm.contains(controlName)) {
|
||||
this.CurrentProductAccountingForm.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 {
|
||||
removeControl('feesReceivableAccountId');
|
||||
removeControl('penaltiesReceivableAccountId');
|
||||
removeControl('interestPayableAccountId');
|
||||
removeControl('interestReceivableAccountId');
|
||||
}
|
||||
|
||||
if (isDormancyActive) {
|
||||
addControl('escheatLiabilityId', false);
|
||||
} else {
|
||||
removeControl('escheatLiabilityId');
|
||||
}
|
||||
} else {
|
||||
removeControl('savingsReferenceAccountId');
|
||||
removeControl('savingsControlAccountId');
|
||||
removeControl('transfersInSuspenseAccountId');
|
||||
removeControl('interestOnSavingsAccountId');
|
||||
removeControl('incomeFromFeeAccountId');
|
||||
removeControl('incomeFromPenaltyAccountId');
|
||||
removeControl('incomeFromInterestId');
|
||||
removeControl('writeOffAccountId');
|
||||
removeControl('overdraftPortfolioControlId');
|
||||
removeControl('feesReceivableAccountId');
|
||||
removeControl('penaltiesReceivableAccountId');
|
||||
removeControl('interestPayableAccountId');
|
||||
removeControl('interestReceivableAccountId');
|
||||
removeControl('escheatLiabilityId');
|
||||
this.clearOtherGlMappings();
|
||||
}
|
||||
}
|
||||
|
||||
get CurrentProductAccounting() {
|
||||
const {
|
||||
assetsOtherGlMappings,
|
||||
liabilitiesOtherGlMappings,
|
||||
expensesOtherGlMappings,
|
||||
incomeOtherGlMappings,
|
||||
...CurrentProductAccounting
|
||||
} = this.CurrentProductAccountingForm.value;
|
||||
const otherGlMappings = this.getOtherGlMappings();
|
||||
|
||||
return {
|
||||
...CurrentProductAccounting,
|
||||
...(otherGlMappings ? { otherGlMappings } : {})
|
||||
};
|
||||
}
|
||||
|
||||
isCashOrAccrualAccounting(): boolean {
|
||||
return this.accounting.isCashOrAccrualAccountingRuleId(this.CurrentProductAccountingForm.value.accountingRule);
|
||||
}
|
||||
|
||||
isAccrualAccounting(): boolean {
|
||||
return this.accounting.isAccrualAccountingRuleId(this.CurrentProductAccountingForm.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
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
<!--
|
||||
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="spch-section">
|
||||
<div class="spch-section__header">
|
||||
<i class="fas fa-receipt"></i>
|
||||
<h3>{{ 'labels.inputs.Charges' | translate }}</h3>
|
||||
</div>
|
||||
<div class="spch-body">
|
||||
<div class="spch-add-row">
|
||||
<mat-form-field appearance="outline" class="spch-add-row__select">
|
||||
<mat-label>{{ 'labels.inputs.Charge' | translate }}</mat-label>
|
||||
<mat-select #charge>
|
||||
@for (charge of chargeData | chargesFilter: chargesDataSource : currencyCode.value; track charge) {
|
||||
<mat-option [value]="charge">
|
||||
{{ charge.name }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
(click)="addCharge(charge)"
|
||||
[disabled]="!charge.value"
|
||||
class="spch-add-row__btn"
|
||||
>
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (chargesDataSource.length > 0) {
|
||||
<div class="spch-table-wrap">
|
||||
<table mat-table [dataSource]="chargesDataSource" class="spch-table">
|
||||
<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>
|
||||
|
||||
<ng-container matColumnDef="action">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
<button mat-icon-button color="warn" (click)="deleteCharge(charge)">
|
||||
<fa-icon icon="trash"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (chargesDataSource.length === 0) {
|
||||
<div class="spch-empty">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<span>{{ 'labels.text.No charges added yet' | translate }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$primary: #1353a0;
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-500: #9aa0a6;
|
||||
$gray-900: #202124;
|
||||
|
||||
.spch-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
&__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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spch-body {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.spch-add-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
|
||||
&__select {
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
&__btn {
|
||||
margin-top: 4px;
|
||||
height: 36px !important;
|
||||
border-radius: 8px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 600 !important;
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
flex-direction: column;
|
||||
|
||||
&__select {
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spch-table-wrap {
|
||||
margin-top: 8px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.spch-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.spch-empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 16px;
|
||||
margin-top: 8px;
|
||||
background: $gray-50;
|
||||
border-radius: 8px;
|
||||
color: $gray-500;
|
||||
font-size: 13px;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { Component, Input, OnInit, inject } from '@angular/core';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
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 { ChargesFilterPipe } from '../../../../pipes/charges-filter.pipe';
|
||||
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-product-charges-step',
|
||||
templateUrl: './current-product-charges-step.component.html',
|
||||
styleUrls: ['./current-product-charges-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatIconButton,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
ChargesFilterPipe,
|
||||
FormatNumberPipe
|
||||
]
|
||||
})
|
||||
export class CurrentProductChargesStepComponent implements OnInit {
|
||||
dialog = inject(MatDialog);
|
||||
private translateService = inject(TranslateService);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
@Input() currencyCode: UntypedFormControl;
|
||||
|
||||
chargeData: any;
|
||||
|
||||
chargesDataSource: {}[];
|
||||
displayedColumns: string[] = [
|
||||
'name',
|
||||
'chargeCalculationType',
|
||||
'amount',
|
||||
'chargeTimeType',
|
||||
'action'
|
||||
];
|
||||
|
||||
pristine = true;
|
||||
|
||||
ngOnInit() {
|
||||
this.chargeData = this.CurrentProductsTemplate.chargeOptions;
|
||||
|
||||
this.chargesDataSource = this.CurrentProductsTemplate.charges || [];
|
||||
this.pristine = true;
|
||||
|
||||
this.currencyCode.valueChanges.subscribe(() => (this.chargesDataSource = []));
|
||||
}
|
||||
|
||||
addCharge(charge: any) {
|
||||
this.chargesDataSource = this.chargesDataSource.concat([charge.value]);
|
||||
charge.value = '';
|
||||
this.pristine = false;
|
||||
}
|
||||
|
||||
deleteCharge(charge: any) {
|
||||
const deleteChargeDialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: { deleteContext: this.translateService.instant('labels.inputs.Charge') + ' ' + charge.name }
|
||||
});
|
||||
deleteChargeDialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.delete) {
|
||||
this.chargesDataSource.splice(this.chargesDataSource.indexOf(charge), 1);
|
||||
this.chargesDataSource = this.chargesDataSource.concat([]);
|
||||
this.pristine = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get CurrentProductCharges() {
|
||||
return {
|
||||
charges: this.chargesDataSource
|
||||
};
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<form [formGroup]="CurrentProductCurrencyForm">
|
||||
<div class="spc-section">
|
||||
<div class="spc-section__header">
|
||||
<i class="fas fa-coins"></i>
|
||||
<span>Currency & Account Status Configuration</span>
|
||||
</div>
|
||||
<div class="spc-body">
|
||||
<div class="spc-grid">
|
||||
<mat-form-field appearance="outline" class="spc-field">
|
||||
<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-hint>Default: Indian Rupee - auto-set for domestic products</mat-hint>
|
||||
<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>
|
||||
<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>Balance Swap Option</h4>
|
||||
<p>Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.</p>
|
||||
</div>
|
||||
<mat-radio-group class="spc-alert__choices" formControlName="balanceSwapOption">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="spc-alert">
|
||||
<div class="spc-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Micro, Small and Medium Enterprises</p>
|
||||
</div>
|
||||
<mat-radio-group class="spc-alert__choices" formControlName="msme">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="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 class="spc-alert">
|
||||
<div class="spc-alert__content">
|
||||
<h4>Auto Debit Facility</h4>
|
||||
<p>Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.</p>
|
||||
</div>
|
||||
<mat-radio-group class="spc-alert__choices" formControlName="autoDebitFacility">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.spc-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.spc-body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.spc-field {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.spc-alert {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
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;
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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, OnInit, Input, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-product-currency-step',
|
||||
templateUrl: './current-product-currency-step.component.html',
|
||||
styleUrls: ['./current-product-currency-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTooltip,
|
||||
MatRadioGroup,
|
||||
MatRadioButton,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class CurrentProductCurrencyStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
|
||||
CurrentProductCurrencyForm: UntypedFormGroup;
|
||||
|
||||
currencyData: any;
|
||||
decimalPlacesData = [
|
||||
2,
|
||||
0,
|
||||
3
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createCurrentProductCurrencyForm();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.currencyData = this.CurrentProductsTemplate.currencyOptions;
|
||||
|
||||
this.CurrentProductCurrencyForm.patchValue({
|
||||
currencyCode: this.CurrentProductsTemplate.currency?.code || this.currencyData?.[0]?.code || '',
|
||||
digitsAfterDecimal:
|
||||
this.CurrentProductsTemplate.digitsAfterDecimal ?? this.CurrentProductsTemplate.currency?.decimalPlaces ?? 2,
|
||||
dormancyAlert: this.CurrentProductsTemplate.dormancyAlert ?? false,
|
||||
balanceSwapOption: this.CurrentProductsTemplate.balanceSwapOption ?? false,
|
||||
msme: this.CurrentProductsTemplate.msme ?? false,
|
||||
nominationFacility: this.CurrentProductsTemplate.nominationFacility ?? false,
|
||||
autoDebitFacility: this.CurrentProductsTemplate.autoDebitFacility ?? false
|
||||
});
|
||||
}
|
||||
|
||||
createCurrentProductCurrencyForm() {
|
||||
this.CurrentProductCurrencyForm = this.formBuilder.group({
|
||||
currencyCode: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
digitsAfterDecimal: [
|
||||
2,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
dormancyAlert: [false],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
nominationFacility: [false],
|
||||
autoDebitFacility: [false]
|
||||
});
|
||||
}
|
||||
|
||||
get CurrentProductCurrency() {
|
||||
const formValue = this.CurrentProductCurrencyForm.value;
|
||||
return {
|
||||
currencyCode: formValue.currencyCode,
|
||||
digitsAfterDecimal: formValue.digitsAfterDecimal,
|
||||
dormancyAlert: formValue.dormancyAlert,
|
||||
balanceSwapOption: formValue.balanceSwapOption,
|
||||
msme: formValue.msme,
|
||||
nominationFacility: formValue.nominationFacility,
|
||||
autoDebitFacility: formValue.autoDebitFacility
|
||||
};
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<form [formGroup]="CurrentProductDetailsForm">
|
||||
<div class="spd-section">
|
||||
<div class="spd-section__header">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<h3>{{ 'labels.inputs.Product Name' | translate }} & {{ 'labels.inputs.Description' | translate }}</h3>
|
||||
</div>
|
||||
<div class="spd-grid">
|
||||
<mat-form-field appearance="outline" class="spd-field">
|
||||
<mat-label>{{ 'labels.inputs.Product Name' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
formControlName="name"
|
||||
matTooltip="{{ 'tooltips.A unique identifier for the current product' | translate }}"
|
||||
required
|
||||
/>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Product Name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="spd-field">
|
||||
<mat-label>{{ 'labels.inputs.Short Name' | translate }}</mat-label>
|
||||
<input
|
||||
matInput
|
||||
formControlName="shortName"
|
||||
matTooltip="{{ 'tooltips.A unique identifier for the current product' | translate }}"
|
||||
maxlength="4"
|
||||
required
|
||||
/>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Short Name' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="spd-field spd-field--full">
|
||||
<mat-label>{{ 'labels.inputs.Description' | translate }}</mat-label>
|
||||
<textarea
|
||||
matInput
|
||||
matTooltip="{{ 'tooltips.Provides additional information' | translate }}"
|
||||
formControlName="description"
|
||||
cdkTextareaAutosize
|
||||
cdkAutosizeMinRows="2"
|
||||
></textarea>
|
||||
</mat-form-field>
|
||||
</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" readonly />
|
||||
</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>
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.spd-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
&__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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.spd-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.spd-grid--classification {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.spd-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.spd-checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 56px;
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* 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, OnInit, OnChanges, Input, SimpleChanges, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-product-details-step',
|
||||
templateUrl: './current-product-details-step.component.html',
|
||||
styleUrls: ['./current-product-details-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTooltip,
|
||||
CdkTextareaAutosize,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class CurrentProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
|
||||
CurrentProductDetailsForm: UntypedFormGroup;
|
||||
accountTypeData: string[] = [];
|
||||
customerSegmentData: string[] = [];
|
||||
yesNoOptions: Array<{
|
||||
value: boolean;
|
||||
label: string;
|
||||
}> = [
|
||||
{
|
||||
value: true,
|
||||
label: 'Yes'
|
||||
},
|
||||
{
|
||||
value: false,
|
||||
label: 'No'
|
||||
}
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createCurrentProductDetailsForm();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this.CurrentProductsTemplate) {
|
||||
this.applyTemplate(this.CurrentProductsTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.CurrentProductsTemplate?.currentValue) {
|
||||
this.applyTemplate(changes.CurrentProductsTemplate.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
createCurrentProductDetailsForm() {
|
||||
this.CurrentProductDetailsForm = this.formBuilder.group({
|
||||
name: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
shortName: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
description: [''],
|
||||
accountType: [
|
||||
null,
|
||||
Validators.required
|
||||
],
|
||||
customerSegment: [null],
|
||||
productCode: [''],
|
||||
effectiveTo: [''],
|
||||
endDate: [''],
|
||||
introducerRequired: [false],
|
||||
shareLinkingEnabled: [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 parseDate(dateValue: any): Date | null {
|
||||
if (!dateValue) return null;
|
||||
|
||||
// Handle array format [year, month, day]
|
||||
if (Array.isArray(dateValue)) {
|
||||
return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]);
|
||||
}
|
||||
|
||||
// Handle Date object
|
||||
if (dateValue instanceof Date) {
|
||||
return dateValue;
|
||||
}
|
||||
|
||||
// Handle string format
|
||||
return new Date(dateValue);
|
||||
}
|
||||
|
||||
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.CurrentProductDetailsForm.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 ?? '',
|
||||
effectiveTo: this.parseDate(template.effectiveTo),
|
||||
endDate: this.parseDate(template.endDate),
|
||||
introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes',
|
||||
shareLinkingEnabled: !!template.shareLinkingEnabled
|
||||
});
|
||||
}
|
||||
|
||||
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 CurrentProductDetails() {
|
||||
const formValue = { ...this.CurrentProductDetailsForm.value };
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
if (formValue.effectiveTo instanceof Date) {
|
||||
formValue.effectiveTo = this.dateUtils.formatDate(formValue.effectiveTo, dateFormat) || '';
|
||||
}
|
||||
|
||||
if (formValue.endDate instanceof Date) {
|
||||
formValue.endDate = this.dateUtils.formatDate(formValue.endDate, dateFormat) || '';
|
||||
}
|
||||
|
||||
return formValue;
|
||||
}
|
||||
}
|
||||
+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-current-product-limits-step',
|
||||
templateUrl: './current-product-limits-step.component.html',
|
||||
styleUrls: ['./current-product-limits-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule
|
||||
]
|
||||
})
|
||||
export class CurrentProductLimitsStepComponent implements OnChanges {
|
||||
@Input() CurrentProductsTemplate: 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.CurrentProductsTemplate?.currentValue) {
|
||||
const template = changes.CurrentProductsTemplate.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 CurrentProductLimits() {
|
||||
return {
|
||||
accountFacilityConfig: { ...this.accountFacilityConfig },
|
||||
accountFacilityLimits: this.accountFacilityLimits.map((row) => ({ ...row })),
|
||||
depositLimitKyc: {
|
||||
...this.depositLimitKyc
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+799
@@ -0,0 +1,799 @@
|
||||
<!--
|
||||
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="responsive-column">
|
||||
<h2 class="mat-h2 flex-100">{{ CurrentProduct.name }}</h2>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.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">{{ CurrentProduct.shortName }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.description) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.description }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
CurrentProduct.accountType ||
|
||||
CurrentProduct.customerSegment ||
|
||||
CurrentProduct.productCode ||
|
||||
CurrentProduct.effectiveTo ||
|
||||
CurrentProduct.introducerRequired !== undefined ||
|
||||
CurrentProduct.shareLinkingEnabled !== undefined
|
||||
) {
|
||||
<h3 class="mat-h3 flex-100">Product Classification</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
@if (CurrentProduct.accountType) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Account Type:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.accountType }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.customerSegment) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Customer Segment:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.customerSegment }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.productCode) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Product Code:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.productCode }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.effectiveTo) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Effective To:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.effectiveTo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.endDate) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">End Date:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.endDate }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.introducerRequired !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Introducer Required:</span>
|
||||
<span class="flex-60">{{
|
||||
CurrentProduct.introducerRequired === true || CurrentProduct.introducerRequired === 'Yes' ? 'Yes' : 'No'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.shareLinkingEnabled !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Share Linking Enabled:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.shareLinkingEnabled | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
CurrentProduct.currency?.name ||
|
||||
CurrentProduct.currency?.displayLabel ||
|
||||
(CurrentProduct.currencyCode | find: CurrentProductsTemplate?.currencyOptions : 'code' : 'name') ||
|
||||
CurrentProduct.currencyCode ||
|
||||
'-'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.digitsAfterDecimal ?? CurrentProduct.currency?.decimalPlaces ?? 2 }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.currency?.inMultiplesOf !== undefined && CurrentProduct.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">{{ CurrentProduct.currency.inMultiplesOf }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.dormancyAlert !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Dormancy Alert:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.dormancyAlert | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.balanceSwapOption !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Balance Swap Option:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.balanceSwapOption | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.msme !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">MSME:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.msme | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.nominationFacility !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Nomination Facility:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.nominationFacility | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.autoDebitFacility !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Auto Debit Facility:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.autoDebitFacility | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">Interest On OD</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Interest Posting Frequency:</span>
|
||||
<span class="flex-60">{{
|
||||
CurrentProduct.interestPostingPeriodType
|
||||
| find: CurrentProductsTemplate?.interestPostingPeriodTypeOptions : 'id' : 'value'
|
||||
| translateKey: 'catalogs'
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Auto Interest Post:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.autoPost | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (
|
||||
CurrentProduct.frequencyFixDay !== undefined &&
|
||||
CurrentProduct.frequencyFixDay !== null &&
|
||||
CurrentProduct.frequencyFixDay !== ''
|
||||
) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Frequency (Fix Day):</span>
|
||||
<span class="flex-60">{{ CurrentProduct.frequencyFixDay }} Days</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.lastInterestDate) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Last Interest Date:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.lastInterestDate }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Settings' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
@if (CurrentProduct.minRequiredOpeningBalance) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Minimum Opening Balance' | translate }}:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.minRequiredOpeningBalance | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.lockinPeriodFrequency) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Lock-in Period' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
CurrentProduct.lockinPeriodFrequency +
|
||||
' ' +
|
||||
(CurrentProduct.lockinPeriodFrequencyType
|
||||
| find: CurrentProductsTemplate?.lockinPeriodFrequencyTypeOptions : 'id' : 'value')
|
||||
| translateKey: 'catalogs'
|
||||
}}</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">{{ CurrentProduct.withdrawalFeeForTransfers | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.balanceFrequency) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Balance Frequency:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.balanceFrequency }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Enable Lock-in Period:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.enableLockinPeriod | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.enableLockinPeriod && CurrentProduct.lockinPeriodFrequency) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Lock-in Period:</span>
|
||||
<span class="flex-60">
|
||||
{{ CurrentProduct.lockinPeriodFrequency }}
|
||||
{{ getLockinPeriodFrequencyTypeLabel(CurrentProduct.lockinPeriodFrequencyType) }}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.minBalanceForInterestCalculation) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.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">{{ CurrentProduct.enforceMinRequiredBalance | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.minRequiredBalance) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Minimum Balance' | translate }}:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.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">{{ CurrentProduct.withHoldTax | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.withHoldTax) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Tax Group' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
CurrentProduct.taxGroupId | find: CurrentProductsTemplate?.taxGroupOptions : 'id' : 'name'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Account Closed with Zero Balance in Day Closing:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.accountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
<h3 class="mat-h3 flex-100">Overdraft (OD) Facility</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Is Overdraft Allowed:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.allowOverdraft | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.allowOverdraft) {
|
||||
@if (CurrentProduct.avgBalanceCalcPeriod) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Avg Balance Calculation Period:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.avgBalanceCalcPeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.validForPeriod) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Valid for Period:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.validForPeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.overdraftLimit !== undefined && CurrentProduct.overdraftLimit !== '') {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">OD Limit %:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.overdraftLimit }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
CurrentProduct.nominalAnnualInterestRateOverdraft !== undefined &&
|
||||
CurrentProduct.nominalAnnualInterestRateOverdraft !== ''
|
||||
) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Interest on OD %:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.nominalAnnualInterestRateOverdraft }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">OD Account Closed with Zero Balance in Day Closing:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.odAccountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Enable Dormancy Tracking' | translate }}:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.isDormancyTrackingActive | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.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">{{ CurrentProduct.daysToInactive }}</span>
|
||||
<span class="flex-40">{{ 'labels.inputs.Number of Days to Dormant sub-status' | translate }}:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.daysToDormancy }}</span>
|
||||
<span class="flex-40">{{ 'labels.inputs.Number of Days to Escheat' | translate }}:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.daysToEscheat }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">Account Facilities</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
@if (CurrentProduct.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">{{ CurrentProduct.accountFacilityConfig[feature.key] | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.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]="CurrentProduct.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 (CurrentProduct.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"
|
||||
>{{ CurrentProduct.depositLimitKyc.general.kycType }} -
|
||||
{{ CurrentProduct.depositLimitKyc.general.limit }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Minor:</span>
|
||||
<span class="flex-60"
|
||||
>{{ CurrentProduct.depositLimitKyc.minor.kycType }} - {{ CurrentProduct.depositLimitKyc.minor.limit }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Small Saving:</span>
|
||||
<span class="flex-60">
|
||||
{{ CurrentProduct.depositLimitKyc.smallSaving.kycType }} -
|
||||
{{ CurrentProduct.depositLimitKyc.smallSaving.limit }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CurrentProduct.charges.length) {
|
||||
<div class="layout-row-wrap responsive-column flex-100">
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Charges' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="CurrentProduct.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>
|
||||
@if (isCashOrAccrualAccounting()) {
|
||||
<div class="spa-gl-summary flex-100">
|
||||
<div class="spa-gl-summary__header">
|
||||
<h4 class="mat-h4">GL Mapping</h4>
|
||||
</div>
|
||||
|
||||
<div class="spa-gl-section">
|
||||
<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(
|
||||
CurrentProduct.savingsReferenceAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.overdraftPortfolioControlId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.interestReceivableAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.feesReceivableAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.penaltiesReceivableAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.savingsControlAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.transfersInSuspenseAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.interestPayableAccountId,
|
||||
CurrentProductsTemplate?.accountingMappingOptions.liabilityAccountOptions
|
||||
)
|
||||
}}</span>
|
||||
</div>
|
||||
@if (CurrentProduct.isDormancyTrackingActive) {
|
||||
<div class="spa-gl-row">
|
||||
<span class="spa-gl-row__label">Escheat liability account</span>
|
||||
<span class="spa-gl-row__value">{{
|
||||
formatGlAccountById(
|
||||
CurrentProduct.escheatLiabilityId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.interestOnSavingsAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.writeOffAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.incomeFromFeeAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.incomeFromPenaltyAccountId,
|
||||
CurrentProductsTemplate?.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(
|
||||
CurrentProduct.incomeFromInterestId,
|
||||
CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions
|
||||
)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (CurrentProduct.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 (CurrentProduct.otherGlMappings.assets?.length) {
|
||||
<table
|
||||
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="CurrentProduct.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, CurrentProductsTemplate?.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 (CurrentProduct.otherGlMappings.liabilities?.length) {
|
||||
<table
|
||||
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="CurrentProduct.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,
|
||||
CurrentProductsTemplate?.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 (CurrentProduct.otherGlMappings.expenses?.length) {
|
||||
<table
|
||||
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="CurrentProduct.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,
|
||||
CurrentProductsTemplate?.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 (CurrentProduct.otherGlMappings.income?.length) {
|
||||
<table
|
||||
class="spa-other-gl-table flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="CurrentProduct.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,
|
||||
CurrentProductsTemplate?.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 (
|
||||
CurrentProduct.advancedAccountingRules &&
|
||||
(CurrentProduct.paymentChannelToFundSourceMappings?.length ||
|
||||
CurrentProduct.feeToIncomeAccountMappings?.length ||
|
||||
CurrentProduct.penaltyToIncomeAccountMappings?.length)
|
||||
) {
|
||||
<div class="layout-row-wrap responsive-column flex-100">
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
@if (CurrentProduct.paymentChannelToFundSourceMappings?.length) {
|
||||
<div class="flex-100 layout-row-wrap responsive-column">
|
||||
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Fund Sources for Payment Channels' | translate }}</h4>
|
||||
<table
|
||||
class="mat-elevation-z1 flex-100"
|
||||
mat-table
|
||||
[dataSource]="CurrentProduct.paymentChannelToFundSourceMappings"
|
||||
>
|
||||
<ng-container matColumnDef="paymentTypeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payment Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let paymentFundSource">
|
||||
{{ paymentFundSource.paymentTypeId | find: CurrentProductsTemplate.paymentTypeOptions : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fundSourceAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fund Source' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let paymentFundSource">
|
||||
{{
|
||||
paymentFundSource.fundSourceAccountId
|
||||
| find: CurrentProductsTemplate?.accountingMappingOptions.assetAccountOptions : 'id' : 'name'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@if (CurrentProduct.feeToIncomeAccountMappings?.length) {
|
||||
<div class="flex-100 layout-row-wrap responsive-column">
|
||||
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Fees to Specific Income Accounts' | translate }}</h4>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="CurrentProduct.feeToIncomeAccountMappings">
|
||||
<ng-container matColumnDef="chargeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fees' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let feesIncome">
|
||||
{{ feesIncome.chargeId | find: CurrentProductsTemplate.chargeOptions : '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: CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions : 'id' : 'name'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
@if (CurrentProduct.penaltyToIncomeAccountMappings?.length) {
|
||||
<div class="flex-100 layout-row-wrap responsive-column">
|
||||
<h4 class="mat-h4 flex-100">{{ 'labels.heading.Penalties to Specific Income Accounts' | translate }}</h4>
|
||||
<table
|
||||
class="flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="CurrentProduct.penaltyToIncomeAccountMappings"
|
||||
>
|
||||
<ng-container matColumnDef="chargeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let penaltyIncome">
|
||||
{{ penaltyIncome.chargeId | find: CurrentProductsTemplate.penaltyOptions : '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: CurrentProductsTemplate?.accountingMappingOptions.incomeAccountOptions : 'id' : 'name'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: feesPenaltyIncomeDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
@use 'assets/styles/helper';
|
||||
@use 'assets/styles/colours' as *;
|
||||
|
||||
.tab-container {
|
||||
padding: 1%;
|
||||
margin: 1%;
|
||||
}
|
||||
|
||||
.mat-h3 {
|
||||
margin-top: 10px !important;
|
||||
font-weight: 300;
|
||||
color: $status-approved;
|
||||
}
|
||||
|
||||
.mat-elevation-z1 {
|
||||
margin: 1em 0 1.5em;
|
||||
}
|
||||
|
||||
.interestCarts {
|
||||
float: right;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
|
||||
tr.incentives-row {
|
||||
height: 0;
|
||||
|
||||
> td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.incentives {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mat-card {
|
||||
border-radius: 0;
|
||||
|
||||
> h4 {
|
||||
font-weight: 500;
|
||||
margin: 0.5em 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
mat-divider {
|
||||
margin: 0 0 0.5em;
|
||||
}
|
||||
|
||||
.margin-t {
|
||||
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;
|
||||
}
|
||||
}
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* 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, OnInit, Input, Output, EventEmitter, OnChanges, SimpleChanges, inject } from '@angular/core';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { OptionData } from 'app/shared/models/option-data.model';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { FindPipe } from '../../../../pipes/find.pipe';
|
||||
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
|
||||
import { YesnoPipe } from '../../../../pipes/yesno.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-product-preview-step',
|
||||
templateUrl: './current-product-preview-step.component.html',
|
||||
styleUrls: ['./current-product-preview-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatDivider,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
FaIconComponent,
|
||||
FindPipe,
|
||||
FormatNumberPipe,
|
||||
YesnoPipe
|
||||
]
|
||||
})
|
||||
export class CurrentProductPreviewStepComponent implements OnInit, OnChanges {
|
||||
private accounting = inject(Accounting);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
@Input() accountingRuleData: any;
|
||||
@Input() CurrentProduct: any;
|
||||
@Input() taskPermission: string;
|
||||
@Output() submitEvent = new EventEmitter();
|
||||
|
||||
chargesDisplayedColumns: string[] = [
|
||||
'name',
|
||||
'chargeCalculationType',
|
||||
'amount',
|
||||
'chargeTimeType'
|
||||
];
|
||||
paymentFundSourceDisplayedColumns: string[] = [
|
||||
'paymentTypeId',
|
||||
'fundSourceAccountId'
|
||||
];
|
||||
feesPenaltyIncomeDisplayedColumns: string[] = [
|
||||
'chargeId',
|
||||
'incomeAccountId'
|
||||
];
|
||||
|
||||
accountingMappings: any = {};
|
||||
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() {
|
||||
this.setCurrentValues();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
this.setCurrentValues();
|
||||
}
|
||||
|
||||
setCurrentValues(): void {
|
||||
if (!this.CurrentProduct || !this.CurrentProductsTemplate) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.interestCalculationTypeOptionData =
|
||||
this.CurrentProductsTemplate.interestCalculationTypeOption ||
|
||||
this.CurrentProductsTemplate.interestCalculationTypeOptions ||
|
||||
[];
|
||||
this.accountingRule = this.accounting.getAccountingRuleFrom(this.CurrentProduct.accountingRule);
|
||||
|
||||
if (!this.isCashOrAccrualAccounting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const assetAccountData = this.CurrentProductsTemplate?.accountingMappingOptions?.assetAccountOptions || [];
|
||||
const incomeAccountData = this.CurrentProductsTemplate?.accountingMappingOptions?.incomeAccountOptions || [];
|
||||
const expenseAccountData = this.CurrentProductsTemplate?.accountingMappingOptions?.expenseAccountOptions || [];
|
||||
const liabilityAccountData = this.CurrentProductsTemplate?.accountingMappingOptions?.liabilityAccountOptions || [];
|
||||
|
||||
this.accountingMappings = {
|
||||
savingsReferenceAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.savingsReferenceAccountId,
|
||||
assetAccountData
|
||||
),
|
||||
overdraftPortfolioControl: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.overdraftPortfolioControlId,
|
||||
assetAccountData
|
||||
),
|
||||
savingsControlAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.savingsControlAccountId,
|
||||
liabilityAccountData
|
||||
),
|
||||
transfersInSuspenseAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.transfersInSuspenseAccountId,
|
||||
liabilityAccountData
|
||||
),
|
||||
interestOnSavingsAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.interestOnSavingsAccountId,
|
||||
expenseAccountData
|
||||
),
|
||||
writeOffAccount: this.accounting.glAccountLookUp(this.CurrentProduct.writeOffAccountId, expenseAccountData),
|
||||
incomeFromFeeAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.incomeFromFeeAccountId,
|
||||
incomeAccountData
|
||||
),
|
||||
incomeFromPenaltyAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.incomeFromPenaltyAccountId,
|
||||
incomeAccountData
|
||||
),
|
||||
incomeFromInterest: this.accounting.glAccountLookUp(this.CurrentProduct.incomeFromInterestId, incomeAccountData)
|
||||
};
|
||||
|
||||
if (this.isAccrualAccounting()) {
|
||||
this.accountingMappings = {
|
||||
...this.accountingMappings,
|
||||
feeReceivableAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.feesReceivableAccountId,
|
||||
assetAccountData
|
||||
),
|
||||
penaltyReceivableAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.penaltiesReceivableAccountId,
|
||||
assetAccountData
|
||||
),
|
||||
interestPayableAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.interestPayableAccountId,
|
||||
liabilityAccountData
|
||||
),
|
||||
interestReceivableAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.interestReceivableAccountId,
|
||||
assetAccountData
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
if (this.CurrentProduct.isDormancyTrackingActive) {
|
||||
this.accountingMappings = {
|
||||
...this.accountingMappings,
|
||||
escheatLiabilityAccount: this.accounting.glAccountLookUp(
|
||||
this.CurrentProduct.escheatLiabilityId,
|
||||
liabilityAccountData
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
isCashOrAccrualAccounting(): boolean {
|
||||
return this.accounting.isCashOrAccrualAccounting(this.accountingRule);
|
||||
}
|
||||
|
||||
isAccrualAccounting(): boolean {
|
||||
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;
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<form [formGroup]="CurrentProductSettingsForm">
|
||||
<!-- ── Balance & Minimum Balance ──────────────────────────────────────── -->
|
||||
<div class="sps-section">
|
||||
<div class="sps-section__header">
|
||||
<span class="sps-section__badge"><i class="fas fa-balance-scale"></i></span>
|
||||
<span>Balance & Minimum Balance</span>
|
||||
</div>
|
||||
<div class="sps-body">
|
||||
<div class="sps-layout sps-layout--balance">
|
||||
<div class="sps-row sps-row--three">
|
||||
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||
<mat-label>Minimum Opening Balance</mat-label>
|
||||
<input type="number" matInput formControlName="minRequiredOpeningBalance" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
|
||||
<div class="sps-toggle sps-toggle--inline">
|
||||
<mat-checkbox labelPosition="before" formControlName="enforceMinRequiredBalance">
|
||||
<span>Enforce Minimum Balance</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||
<mat-label>Minimum Balance (₹) *</mat-label>
|
||||
<input type="number" matInput formControlName="minRequiredBalance" min="0" step="0.01" />
|
||||
<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>
|
||||
|
||||
<!-- ── Lock-in Period ─────────────────────────────────────────────────── -->
|
||||
<div class="sps-section">
|
||||
<div class="sps-section__header">
|
||||
<span class="sps-section__badge"><i class="fas fa-lock"></i></span>
|
||||
<span>Lock-in Period</span>
|
||||
</div>
|
||||
<div class="sps-body">
|
||||
<div class="sps-lockin-row">
|
||||
<span>Enable Lock-in Period</span>
|
||||
<mat-checkbox formControlName="enableLockinPeriod"></mat-checkbox>
|
||||
</div>
|
||||
|
||||
@if (CurrentProductSettingsForm.value.enableLockinPeriod) {
|
||||
<div class="sps-lockin-fields">
|
||||
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||
<mat-label>Frequency *</mat-label>
|
||||
<input type="number" matInput formControlName="lockinPeriodFrequency" min="1" step="1" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="sps-field sps-field--compact">
|
||||
<mat-label>Type *</mat-label>
|
||||
<mat-select formControlName="lockinPeriodFrequencyType">
|
||||
@for (type of lockinPeriodFrequencyTypeData; track type) {
|
||||
<mat-option [value]="type.id">
|
||||
{{ type.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Additional Rules ───────────────────────────────────────────────── -->
|
||||
<div class="sps-section">
|
||||
<div class="sps-section__header">
|
||||
<span class="sps-section__badge"><i class="fas fa-cog"></i></span>
|
||||
<span>Additional Rules</span>
|
||||
</div>
|
||||
<div class="sps-body">
|
||||
<div class="sps-toggle sps-toggle--inline">
|
||||
<mat-checkbox formControlName="accountClosedZeroBalanceInDayClosing">
|
||||
<span>Account Closed with Zero Balance in Day Closing</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
<div class="sps-toggle sps-toggle--inline">
|
||||
<mat-checkbox formControlName="withHoldTax">
|
||||
<span>Withhold Tax is Applicable</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Overdraft (OD) Facility ────────────────────────────────────────── -->
|
||||
<div class="sps-section">
|
||||
<div class="sps-section__header">
|
||||
<span class="sps-section__badge"><i class="fas fa-hand-holding-usd"></i></span>
|
||||
<span>Overdraft (OD) Facility</span>
|
||||
</div>
|
||||
<div class="sps-body">
|
||||
<div class="sps-od-layout">
|
||||
<!-- Left: checkboxes -->
|
||||
<div class="sps-od-checks">
|
||||
<div class="sps-od-check-row">
|
||||
<mat-checkbox formControlName="allowOverdraft">
|
||||
<span>Is Overdraft Allowed</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
@if (CurrentProductSettingsForm.value.allowOverdraft) {
|
||||
<div class="sps-od-check-row">
|
||||
<mat-checkbox formControlName="odAccountClosedZeroBalanceInDayClosing">
|
||||
<span>OD Account Closed with Zero Balance in Day Closing</span>
|
||||
</mat-checkbox>
|
||||
<p class="sps-od-check-hint">
|
||||
Automatically closes OD accounts when balance reaches zero during day-end closing.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Right: OD details panel — only visible when OD is allowed -->
|
||||
@if (CurrentProductSettingsForm.value.allowOverdraft) {
|
||||
<div class="sps-od-panel">
|
||||
<div class="sps-od-radio-group">
|
||||
<span class="sps-od-radio-label">Average Balance Calculation in Period</span>
|
||||
<mat-radio-group formControlName="avgBalanceCalcPeriod" class="sps-od-radio-choices">
|
||||
<mat-radio-button value="6M">6M</mat-radio-button>
|
||||
<mat-radio-button value="1Y">1Y</mat-radio-button>
|
||||
<mat-radio-button value="3Y">3Y</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="sps-od-radio-group">
|
||||
<span class="sps-od-radio-label">Valid for Period</span>
|
||||
<mat-radio-group formControlName="validForPeriod" class="sps-od-radio-choices">
|
||||
<mat-radio-button value="3M">3M</mat-radio-button>
|
||||
<mat-radio-button value="1Y">1Y</mat-radio-button>
|
||||
<mat-radio-button value="3Y">3Y</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="sps-od-inputs">
|
||||
<mat-form-field appearance="outline" class="sps-field">
|
||||
<mat-label>OD Limit %</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="overdraftLimit"
|
||||
placeholder="e.g. 15"
|
||||
min="0"
|
||||
step="0.01"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="sps-field">
|
||||
<mat-label>Interest on OD %</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="nominalAnnualInterestRateOverdraft"
|
||||
placeholder="e.g. 8.5"
|
||||
min="0"
|
||||
step="0.01"
|
||||
/>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.sps-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sps-section__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sps-body {
|
||||
background: #fff;
|
||||
padding: 18px 24px 24px;
|
||||
}
|
||||
|
||||
.sps-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
&--compact {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
align-items: center;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Overdraft (OD) Facility ───────────────────────────────────────────────
|
||||
|
||||
.sps-od-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
align-items: start;
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.sps-od-checks {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.sps-od-check-row {
|
||||
padding: 14px 16px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
background: $gray-50;
|
||||
|
||||
mat-checkbox {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.sps-od-check-hint {
|
||||
margin: 6px 0 0 28px;
|
||||
font-size: 11.5px;
|
||||
color: #64748b;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.sps-od-panel {
|
||||
background: #f0f7ff;
|
||||
border: 1px solid #bfdbfe;
|
||||
border-radius: 10px;
|
||||
padding: 20px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.sps-od-radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #dbeafe;
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.sps-od-radio-label {
|
||||
font-size: 10.5px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.07em;
|
||||
color: #3b82f6;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.sps-od-radio-choices {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sps-od-inputs {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
|
||||
@media (width <= 640px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* 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, OnInit, Input, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-product-settings-step',
|
||||
templateUrl: './current-product-settings-step.component.html',
|
||||
styleUrls: ['./current-product-settings-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatCheckbox,
|
||||
MatRadioGroup,
|
||||
MatRadioButton
|
||||
]
|
||||
})
|
||||
export class CurrentProductSettingsStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
|
||||
CurrentProductSettingsForm: UntypedFormGroup;
|
||||
|
||||
periodFrequencyData: any[] = [];
|
||||
|
||||
lockinPeriodFrequencyTypeData = [
|
||||
{ id: 'DAYS', value: 'value.days' },
|
||||
{ id: 'WEEKS', value: 'value.weeks' },
|
||||
{ id: 'MONTHS', value: 'value.months' },
|
||||
{ id: 'YEARS', value: 'value.years' }
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createCurrentProductSettingsForm();
|
||||
this.setConditionalControls();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.periodFrequencyData = this.CurrentProductsTemplate.periodFrequencyOptions || [];
|
||||
const defaultPeriod = this.periodFrequencyData[0]?.value || 'Monthly';
|
||||
const isEditing = !!this.CurrentProductsTemplate.id;
|
||||
|
||||
this.CurrentProductSettingsForm.patchValue({
|
||||
minRequiredOpeningBalance: this.CurrentProductsTemplate.minRequiredOpeningBalance,
|
||||
withdrawalFeeForTransfers: this.CurrentProductsTemplate.withdrawalFeeForTransfers,
|
||||
enforceMinRequiredBalance: this.CurrentProductsTemplate.enforceMinRequiredBalance,
|
||||
minRequiredBalance: this.CurrentProductsTemplate.minRequiredBalance,
|
||||
withHoldTax: this.CurrentProductsTemplate.withHoldTax ?? false,
|
||||
balanceFrequency: isEditing ? this.CurrentProductsTemplate.balanceFrequency || defaultPeriod : defaultPeriod,
|
||||
enableLockinPeriod: !!(
|
||||
this.CurrentProductsTemplate.lockinPeriodFrequency && this.CurrentProductsTemplate.lockinPeriodFrequency > 0
|
||||
),
|
||||
accountClosedZeroBalanceInDayClosing: isEditing
|
||||
? (this.CurrentProductsTemplate.accountClosedZeroBalanceInDayClosing ?? false)
|
||||
: false,
|
||||
isDormancyTrackingActive: isEditing ? (this.CurrentProductsTemplate.isDormancyTrackingActive ?? true) : true,
|
||||
daysToInactive: isEditing ? this.CurrentProductsTemplate.daysToInactive || 30 : 30,
|
||||
daysToDormancy: isEditing ? this.CurrentProductsTemplate.daysToDormancy || 60 : 60,
|
||||
daysToEscheat: isEditing ? this.CurrentProductsTemplate.daysToEscheat || 90 : 90,
|
||||
// Overdraft fields
|
||||
allowOverdraft: isEditing ? (this.CurrentProductsTemplate.allowOverdraft ?? false) : false,
|
||||
overdraftLimit: isEditing ? this.CurrentProductsTemplate.overdraftLimit : '',
|
||||
nominalAnnualInterestRateOverdraft: isEditing
|
||||
? this.CurrentProductsTemplate.nominalAnnualInterestRateOverdraft
|
||||
: '',
|
||||
avgBalanceCalcPeriod: isEditing ? this.CurrentProductsTemplate.avgBalanceCalcPeriod || '1Y' : '1Y',
|
||||
validForPeriod: isEditing ? this.CurrentProductsTemplate.validForPeriod || '1Y' : '1Y',
|
||||
odAccountClosedZeroBalanceInDayClosing: isEditing
|
||||
? (this.CurrentProductsTemplate.odAccountClosedZeroBalanceInDayClosing ?? false)
|
||||
: false
|
||||
});
|
||||
|
||||
if (
|
||||
isEditing &&
|
||||
this.CurrentProductsTemplate.lockinPeriodFrequency &&
|
||||
this.CurrentProductsTemplate.lockinPeriodFrequency > 0
|
||||
) {
|
||||
const lockinTypeIdToKey: { [key: number]: string } = { 0: 'DAYS', 1: 'WEEKS', 2: 'MONTHS', 3: 'YEARS' };
|
||||
const rawLockinType = this.CurrentProductsTemplate.lockinPeriodFrequencyType;
|
||||
// Backend may return an EnumOptionData {id, code, value} or a plain integer
|
||||
const lockinTypeId = typeof rawLockinType === 'object' ? rawLockinType?.id : rawLockinType;
|
||||
this.CurrentProductSettingsForm.patchValue({
|
||||
lockinPeriodFrequency: this.CurrentProductsTemplate.lockinPeriodFrequency,
|
||||
lockinPeriodFrequencyType: lockinTypeIdToKey[lockinTypeId] || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
createCurrentProductSettingsForm() {
|
||||
this.CurrentProductSettingsForm = this.formBuilder.group({
|
||||
minRequiredOpeningBalance: [
|
||||
'',
|
||||
[Validators.min(0)]
|
||||
],
|
||||
withdrawalFeeForTransfers: [false],
|
||||
enforceMinRequiredBalance: [false],
|
||||
minRequiredBalance: [
|
||||
'',
|
||||
[Validators.min(0)]
|
||||
],
|
||||
withHoldTax: [false],
|
||||
balanceFrequency: ['Monthly'],
|
||||
enableLockinPeriod: [false],
|
||||
lockinPeriodFrequency: [''],
|
||||
lockinPeriodFrequencyType: [''],
|
||||
accountClosedZeroBalanceInDayClosing: [false],
|
||||
// Dormancy (used internally / by accounting step)
|
||||
isDormancyTrackingActive: [true],
|
||||
daysToInactive: [30],
|
||||
daysToDormancy: [60],
|
||||
daysToEscheat: [90],
|
||||
// Overdraft (OD) Facility
|
||||
allowOverdraft: [false],
|
||||
overdraftLimit: [
|
||||
'',
|
||||
[Validators.min(0)]
|
||||
],
|
||||
nominalAnnualInterestRateOverdraft: [
|
||||
'',
|
||||
[Validators.min(0)]
|
||||
],
|
||||
avgBalanceCalcPeriod: ['1Y'],
|
||||
validForPeriod: ['1Y'],
|
||||
odAccountClosedZeroBalanceInDayClosing: [false]
|
||||
});
|
||||
}
|
||||
|
||||
setConditionalControls() {
|
||||
this.CurrentProductSettingsForm.get('enableLockinPeriod').valueChanges.subscribe((enabled: boolean) => {
|
||||
const freq = this.CurrentProductSettingsForm.get('lockinPeriodFrequency');
|
||||
const type = this.CurrentProductSettingsForm.get('lockinPeriodFrequencyType');
|
||||
if (enabled) {
|
||||
freq.setValidators([
|
||||
Validators.required,
|
||||
Validators.min(1),
|
||||
Validators.pattern('^[1-9]\\d*$')
|
||||
]);
|
||||
type.setValidators([Validators.required]);
|
||||
} else {
|
||||
freq.setValue('');
|
||||
type.setValue('');
|
||||
freq.clearValidators();
|
||||
type.clearValidators();
|
||||
}
|
||||
freq.updateValueAndValidity();
|
||||
type.updateValueAndValidity();
|
||||
});
|
||||
}
|
||||
|
||||
get CurrentProductSettings() {
|
||||
const formValue = { ...this.CurrentProductSettingsForm.value };
|
||||
|
||||
if (!formValue.allowOverdraft) {
|
||||
delete formValue.overdraftLimit;
|
||||
delete formValue.nominalAnnualInterestRateOverdraft;
|
||||
delete formValue.avgBalanceCalcPeriod;
|
||||
delete formValue.validForPeriod;
|
||||
delete formValue.odAccountClosedZeroBalanceInDayClosing;
|
||||
}
|
||||
|
||||
return formValue;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<form [formGroup]="CurrentProductTermsForm">
|
||||
<div class="spt-section">
|
||||
<div class="spt-section__header">
|
||||
<span class="spt-section__badge"><i class="fas fa-chart-line"></i></span>
|
||||
<span>Interest On OD</span>
|
||||
</div>
|
||||
<div class="spt-body">
|
||||
<mat-form-field appearance="outline" class="spt-field" style="max-width: 320px">
|
||||
<mat-label>Interest Posting Frequency *</mat-label>
|
||||
<mat-select formControlName="interestPostingPeriodType" required>
|
||||
@for (item of interestPostingPeriodTypeData; track item) {
|
||||
<mat-option [value]="item.id">
|
||||
{{ item.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
Interest Posting Frequency {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<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>
|
||||
|
||||
<div class="spt-note">
|
||||
<span>⚠️</span>
|
||||
<span>Daily product-based interest accrual enabled — interest calculated EOD on daily closing balance.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.spt-section {
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
.spt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.spt-grid--three {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
.spt-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
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;
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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, OnInit, Input, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
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';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-current-product-terms-step',
|
||||
templateUrl: './current-product-terms-step.component.html',
|
||||
styleUrls: ['./current-product-terms-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent
|
||||
]
|
||||
})
|
||||
export class CurrentProductTermsStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
|
||||
CurrentProductTermsForm: UntypedFormGroup;
|
||||
|
||||
interestPostingPeriodTypeData: any[] = [];
|
||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
||||
{ value: true, label: 'Yes' },
|
||||
{ value: false, label: 'No' }
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createCurrentProductTermsForm();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.interestPostingPeriodTypeData = this.CurrentProductsTemplate.interestPostingPeriodTypeOptions || [];
|
||||
this.setYesNoOptions(this.CurrentProductsTemplate?.yesNoOptions);
|
||||
|
||||
const isEditing = !!this.CurrentProductsTemplate.id;
|
||||
|
||||
this.CurrentProductTermsForm.patchValue({
|
||||
interestPostingPeriodType: this.CurrentProductsTemplate.interestPostingPeriodType?.id || '',
|
||||
autoPost: isEditing ? (this.CurrentProductsTemplate.autoPost ?? true) : true,
|
||||
frequencyFixDay: isEditing ? (this.CurrentProductsTemplate.frequencyFixDay ?? 30) : 30,
|
||||
lastInterestDate: this.parseDate(this.CurrentProductsTemplate.lastInterestDate)
|
||||
});
|
||||
}
|
||||
|
||||
private parseDate(dateValue: any): Date | null {
|
||||
if (!dateValue) return null;
|
||||
if (Array.isArray(dateValue)) {
|
||||
return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]);
|
||||
}
|
||||
if (dateValue instanceof Date) {
|
||||
return dateValue;
|
||||
}
|
||||
return new Date(dateValue);
|
||||
}
|
||||
|
||||
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((v: any) => !!v);
|
||||
|
||||
if (mappedOptions.length) {
|
||||
this.yesNoOptions = mappedOptions;
|
||||
}
|
||||
}
|
||||
|
||||
createCurrentProductTermsForm() {
|
||||
this.CurrentProductTermsForm = this.formBuilder.group({
|
||||
interestPostingPeriodType: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
autoPost: [true],
|
||||
frequencyFixDay: [
|
||||
30,
|
||||
[Validators.min(0)]
|
||||
],
|
||||
lastInterestDate: ['']
|
||||
});
|
||||
}
|
||||
|
||||
get CurrentProductTerms() {
|
||||
const formValue = { ...this.CurrentProductTermsForm.value };
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
if (formValue.lastInterestDate instanceof Date) {
|
||||
formValue.lastInterestDate = this.dateUtils.formatDate(formValue.lastInterestDate, dateFormat) || '';
|
||||
}
|
||||
|
||||
return { ...formValue };
|
||||
}
|
||||
}
|
||||
+137
-52
@@ -1,69 +1,154 @@
|
||||
<div class="container">
|
||||
<mat-stepper orientation="vertical" [linear]="false" class="mat-elevation-z8" labelPosition="bottom">
|
||||
<ng-template matStepperIcon="number"><fa-icon icon="pencil-alt" size="sm"></fa-icon></ng-template>
|
||||
<ng-template matStepperIcon="edit"><fa-icon icon="pencil-alt" size="sm"></fa-icon></ng-template>
|
||||
<ng-template matStepperIcon="done"><fa-icon icon="check" size="sm"></fa-icon></ng-template>
|
||||
<ng-template matStepperIcon="error"><fa-icon icon="exclamation-triangle" size="lg"></fa-icon></ng-template>
|
||||
<ng-template matStepperIcon="preview"><fa-icon icon="eye" size="sm"></fa-icon></ng-template>
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
<mat-step [stepControl]="detailsForm" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.DETAILS' | translate }}</ng-template>
|
||||
<mifosx-saving-product-details-step
|
||||
[savingProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-saving-product-details-step>
|
||||
</mat-step>
|
||||
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/.
|
||||
-->
|
||||
|
||||
<mat-step [stepControl]="currencyForm" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CURRENCY' | translate }}</ng-template>
|
||||
<mifosx-saving-product-currency-step
|
||||
[savingProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-saving-product-currency-step>
|
||||
</mat-step>
|
||||
<div class="cp-container">
|
||||
<div class="cp-header">
|
||||
<i class="fas fa-credit-card cp-header__icon"></i>
|
||||
<h2 class="cp-header__title">{{ 'labels.heading.Edit Current Product' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<mat-step [stepControl]="termsForm" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.TERMS' | translate }}</ng-template>
|
||||
<mifosx-saving-product-terms-step
|
||||
[savingProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-saving-product-terms-step>
|
||||
</mat-step>
|
||||
<mat-tab-group
|
||||
class="cp-tabs"
|
||||
animationDuration="200ms"
|
||||
[preserveContent]="true"
|
||||
[(selectedIndex)]="selectedTabIndex"
|
||||
>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-info-circle cp-tab-icon"></i>
|
||||
{{ 'labels.inputs.DETAILS' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-details-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-current-product-details-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-step [stepControl]="settingsForm" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.SETTINGS' | translate }}</ng-template>
|
||||
<mifosx-saving-product-settings-step
|
||||
[savingProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-saving-product-settings-step>
|
||||
</mat-step>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-coins cp-tab-icon"></i>
|
||||
{{ 'labels.inputs.CURRENCY' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-currency-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-current-product-currency-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-step completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.CHARGES' | translate }}</ng-template>
|
||||
<mifosx-saving-product-charges-step
|
||||
[savingProductsTemplate]="currentProductAndTemplate"
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-sliders-h cp-tab-icon"></i>
|
||||
{{ 'labels.inputs.TERMS' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-terms-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-current-product-terms-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-cog cp-tab-icon"></i>
|
||||
{{ 'labels.inputs.SETTINGS' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-settings-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-current-product-settings-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-receipt cp-tab-icon"></i>
|
||||
{{ 'labels.inputs.CHARGES' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-charges-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
[currencyCode]="currencyForm.get('currencyCode')"
|
||||
></mifosx-saving-product-charges-step>
|
||||
</mat-step>
|
||||
></mifosx-current-product-charges-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-step [stepControl]="accountingForm" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.ACCOUNTING' | translate }}</ng-template>
|
||||
<mifosx-saving-product-accounting-step
|
||||
[savingProductsTemplate]="currentProductAndTemplate"
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-mobile-alt cp-tab-icon"></i>
|
||||
Digital
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-limits-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
></mifosx-current-product-limits-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-book cp-tab-icon"></i>
|
||||
{{ 'labels.inputs.ACCOUNTING' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-accounting-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[isDormancyTrackingActive]="settingsForm.get('isDormancyTrackingActive')"
|
||||
[savingProductFormValid]="formValidAndNotPristine"
|
||||
[CurrentProductFormValid]="formValidAndNotPristine"
|
||||
[allowOverdraft]="settingsForm.get('allowOverdraft')"
|
||||
></mifosx-saving-product-accounting-step>
|
||||
</mat-step>
|
||||
></mifosx-current-product-accounting-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
@if (formValidAndNotPristine) {
|
||||
<mat-step state="preview" completed>
|
||||
<ng-template matStepLabel>{{ 'labels.inputs.PREVIEW' | translate }}</ng-template>
|
||||
<mifosx-saving-product-preview-step
|
||||
[savingProductsTemplate]="currentProductAndTemplate"
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-eye cp-tab-icon"></i>
|
||||
{{ 'labels.inputs.PREVIEW' | translate }}
|
||||
</ng-template>
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-preview-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
[accountingRuleData]="accountingRuleData"
|
||||
[savingProduct]="currentProduct"
|
||||
[CurrentProduct]="currentProduct"
|
||||
[taskPermission]="'UPDATE_CURRENTPRODUCT'"
|
||||
(submitEvent)="submit()"
|
||||
></mifosx-saving-product-preview-step>
|
||||
</mat-step>
|
||||
></mifosx-current-product-preview-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
}
|
||||
</mat-stepper>
|
||||
</mat-tab-group>
|
||||
|
||||
<div class="cp-action-bar">
|
||||
<button mat-raised-button [routerLink]="['../../']" class="cp-action-bar__cancel">
|
||||
{{ 'labels.buttons.Cancel' | translate }}
|
||||
</button>
|
||||
<div class="cp-action-bar__right">
|
||||
@if (selectedTabIndex > 0) {
|
||||
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
||||
<i class="fas fa-arrow-left" style="margin-right: 8px"></i>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (selectedTabIndex < totalTabs - 1) {
|
||||
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<i class="fas fa-arrow-right" style="margin-left: 8px"></i>
|
||||
</button>
|
||||
}
|
||||
@if (formValidAndNotPristine && selectedTabIndex === totalTabs - 1) {
|
||||
<button mat-raised-button color="primary" (click)="submit()">
|
||||
<i class="fas fa-check" style="margin-right: 8px"></i>
|
||||
{{ 'labels.buttons.Submit' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+89
-3
@@ -1,4 +1,90 @@
|
||||
fa-icon {
|
||||
position: relative;
|
||||
left: 5%;
|
||||
/**
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
$primary: #1353a0;
|
||||
|
||||
.cp-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.cp-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__icon {
|
||||
font-size: 20px;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.cp-tabs {
|
||||
background: var(--md-sys-color-surface, #fff);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||
|
||||
::ng-deep {
|
||||
.mat-mdc-tab-header {
|
||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
.mat-mdc-tab {
|
||||
min-width: 120px;
|
||||
padding: 0 16px;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-wrapper {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp-tab-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cp-tab-content {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.cp-action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
|
||||
&__cancel {
|
||||
color: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
+85
-44
@@ -1,17 +1,24 @@
|
||||
/**
|
||||
* 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, ViewChild, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { SavingProductDetailsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-details-step/saving-product-details-step.component';
|
||||
import { SavingProductCurrencyStepComponent } from '../../saving-products/saving-product-stepper/saving-product-currency-step/saving-product-currency-step.component';
|
||||
import { SavingProductTermsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-terms-step/saving-product-terms-step.component';
|
||||
import { SavingProductSettingsStepComponent } from '../../saving-products/saving-product-stepper/saving-product-settings-step/saving-product-settings-step.component';
|
||||
import { SavingProductChargesStepComponent } from '../../saving-products/saving-product-stepper/saving-product-charges-step/saving-product-charges-step.component';
|
||||
import { SavingProductAccountingStepComponent } from '../../saving-products/saving-product-stepper/saving-product-accounting-step/saving-product-accounting-step.component';
|
||||
import { SavingProductPreviewStepComponent } from '../../saving-products/saving-product-stepper/saving-product-preview-step/saving-product-preview-step.component';
|
||||
import { CurrentProductDetailsStepComponent } from '../current-product-stepper/current-product-details-step/current-product-details-step.component';
|
||||
import { CurrentProductCurrencyStepComponent } from '../current-product-stepper/current-product-currency-step/current-product-currency-step.component';
|
||||
import { CurrentProductTermsStepComponent } from '../current-product-stepper/current-product-terms-step/current-product-terms-step.component';
|
||||
import { CurrentProductSettingsStepComponent } from '../current-product-stepper/current-product-settings-step/current-product-settings-step.component';
|
||||
import { CurrentProductChargesStepComponent } from '../current-product-stepper/current-product-charges-step/current-product-charges-step.component';
|
||||
import { CurrentProductLimitsStepComponent } from '../current-product-stepper/current-product-limits-step/current-product-limits-step.component';
|
||||
import { CurrentProductAccountingStepComponent } from '../current-product-stepper/current-product-accounting-step/current-product-accounting-step.component';
|
||||
import { CurrentProductPreviewStepComponent } from '../current-product-stepper/current-product-preview-step/current-product-preview-step.component';
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { MatStepper, MatStepperIcon, MatStep, MatStepLabel } from '@angular/material/stepper';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
@@ -20,18 +27,15 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
styleUrls: ['./edit-current-product.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatStepper,
|
||||
MatStepperIcon,
|
||||
FaIconComponent,
|
||||
MatStep,
|
||||
MatStepLabel,
|
||||
SavingProductDetailsStepComponent,
|
||||
SavingProductCurrencyStepComponent,
|
||||
SavingProductTermsStepComponent,
|
||||
SavingProductSettingsStepComponent,
|
||||
SavingProductChargesStepComponent,
|
||||
SavingProductAccountingStepComponent,
|
||||
SavingProductPreviewStepComponent
|
||||
MatTabsModule,
|
||||
CurrentProductDetailsStepComponent,
|
||||
CurrentProductCurrencyStepComponent,
|
||||
CurrentProductTermsStepComponent,
|
||||
CurrentProductSettingsStepComponent,
|
||||
CurrentProductChargesStepComponent,
|
||||
CurrentProductLimitsStepComponent,
|
||||
CurrentProductAccountingStepComponent,
|
||||
CurrentProductPreviewStepComponent
|
||||
]
|
||||
})
|
||||
export class EditCurrentProductComponent {
|
||||
@@ -41,16 +45,18 @@ export class EditCurrentProductComponent {
|
||||
private settingsService = inject(SettingsService);
|
||||
private accounting = inject(Accounting);
|
||||
|
||||
@ViewChild(SavingProductDetailsStepComponent, { static: true }) detailsStep: SavingProductDetailsStepComponent;
|
||||
@ViewChild(SavingProductCurrencyStepComponent, { static: true }) currencyStep: SavingProductCurrencyStepComponent;
|
||||
@ViewChild(SavingProductTermsStepComponent, { static: true }) termsStep: SavingProductTermsStepComponent;
|
||||
@ViewChild(SavingProductSettingsStepComponent, { static: true }) settingsStep: SavingProductSettingsStepComponent;
|
||||
@ViewChild(SavingProductChargesStepComponent, { static: true }) chargesStep: SavingProductChargesStepComponent;
|
||||
@ViewChild(SavingProductAccountingStepComponent, { static: true })
|
||||
accountingStep: SavingProductAccountingStepComponent;
|
||||
@ViewChild(CurrentProductDetailsStepComponent, { static: true }) detailsStep: CurrentProductDetailsStepComponent;
|
||||
@ViewChild(CurrentProductCurrencyStepComponent, { static: true }) currencyStep: CurrentProductCurrencyStepComponent;
|
||||
@ViewChild(CurrentProductTermsStepComponent, { static: true }) termsStep: CurrentProductTermsStepComponent;
|
||||
@ViewChild(CurrentProductSettingsStepComponent, { static: true }) settingsStep: CurrentProductSettingsStepComponent;
|
||||
@ViewChild(CurrentProductChargesStepComponent, { static: true }) chargesStep: CurrentProductChargesStepComponent;
|
||||
@ViewChild(CurrentProductLimitsStepComponent, { static: true }) limitsStep: CurrentProductLimitsStepComponent;
|
||||
@ViewChild(CurrentProductAccountingStepComponent, { static: true })
|
||||
accountingStep: CurrentProductAccountingStepComponent;
|
||||
|
||||
currentProductAndTemplate: any;
|
||||
accountingRuleData: string[] = [];
|
||||
selectedTabIndex = 0;
|
||||
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { currentProductAndTemplate: any }) => {
|
||||
@@ -60,19 +66,26 @@ export class EditCurrentProductComponent {
|
||||
}
|
||||
|
||||
get detailsForm() {
|
||||
return this.detailsStep.savingProductDetailsForm;
|
||||
return this.detailsStep.CurrentProductDetailsForm;
|
||||
}
|
||||
get currencyForm() {
|
||||
return this.currencyStep.savingProductCurrencyForm;
|
||||
return this.currencyStep.CurrentProductCurrencyForm;
|
||||
}
|
||||
get termsForm() {
|
||||
return this.termsStep.savingProductTermsForm;
|
||||
return this.termsStep.CurrentProductTermsForm;
|
||||
}
|
||||
get settingsForm() {
|
||||
return this.settingsStep.savingProductSettingsForm;
|
||||
return this.settingsStep.CurrentProductSettingsForm;
|
||||
}
|
||||
get limitsForm(): void {
|
||||
return; // Limits step doesn't have a form, it's data-driven
|
||||
}
|
||||
get accountingForm() {
|
||||
return this.accountingStep.savingProductAccountingForm;
|
||||
return this.accountingStep.CurrentProductAccountingForm;
|
||||
}
|
||||
|
||||
get totalTabs(): number {
|
||||
return this.formValidAndNotPristine ? 8 : 7;
|
||||
}
|
||||
|
||||
get formValidAndNotPristine() {
|
||||
@@ -87,29 +100,57 @@ export class EditCurrentProductComponent {
|
||||
!this.termsForm.pristine ||
|
||||
!this.settingsForm.pristine ||
|
||||
!this.chargesStep.pristine ||
|
||||
!this.limitsStep.pristine ||
|
||||
!this.accountingForm.pristine)
|
||||
);
|
||||
}
|
||||
|
||||
get currentProduct() {
|
||||
return {
|
||||
...this.detailsStep.savingProductDetails,
|
||||
...this.currencyStep.savingProductCurrency,
|
||||
...this.termsStep.savingProductTerms,
|
||||
...this.settingsStep.savingProductSettings,
|
||||
...this.chargesStep.savingProductCharges,
|
||||
...this.accountingStep.savingProductAccounting
|
||||
...this.detailsStep.CurrentProductDetails,
|
||||
...this.currencyStep.CurrentProductCurrency,
|
||||
...this.termsStep.CurrentProductTerms,
|
||||
...this.settingsStep.CurrentProductSettings,
|
||||
...this.chargesStep.CurrentProductCharges,
|
||||
...this.limitsStep.CurrentProductLimits,
|
||||
...this.accountingStep.CurrentProductAccounting
|
||||
};
|
||||
}
|
||||
|
||||
submit() {
|
||||
const product = {
|
||||
private prepareCurrentProductPayload() {
|
||||
const currentProduct = {
|
||||
...this.currentProduct,
|
||||
charges: this.currentProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code
|
||||
locale: this.settingsService.language.code,
|
||||
dateFormat: 'dd MMMM yyyy'
|
||||
};
|
||||
delete product.advancedAccountingRules;
|
||||
this.productsService.updateCurrentProduct(this.currentProductAndTemplate.id, product).subscribe((response: any) => {
|
||||
delete currentProduct.advancedAccountingRules;
|
||||
|
||||
if (currentProduct.lockinPeriodFrequencyType) {
|
||||
const frequencyTypeMapping: { [key: string]: number } = {
|
||||
DAYS: 0,
|
||||
WEEKS: 1,
|
||||
MONTHS: 2,
|
||||
YEARS: 3
|
||||
};
|
||||
currentProduct.lockinPeriodFrequencyType = frequencyTypeMapping[currentProduct.lockinPeriodFrequencyType];
|
||||
}
|
||||
|
||||
Object.keys(currentProduct).forEach((key) => {
|
||||
if (currentProduct[key] === '' || currentProduct[key] === null || currentProduct[key] === undefined) {
|
||||
delete currentProduct[key];
|
||||
}
|
||||
});
|
||||
|
||||
return currentProduct;
|
||||
}
|
||||
|
||||
submit() {
|
||||
const currentProduct = this.prepareCurrentProductPayload();
|
||||
console.log('Current product update payload:', currentProduct);
|
||||
this.productsService
|
||||
.updateCurrentProduct(this.currentProductAndTemplate.id, currentProduct)
|
||||
.subscribe((response: any) => {
|
||||
this.router.navigate(
|
||||
[
|
||||
'../../',
|
||||
|
||||
+558
-32
@@ -1,3 +1,11 @@
|
||||
<!--
|
||||
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="layout-row align-end gap-2px responsive-column container m-b-20">
|
||||
<button
|
||||
mat-raised-button
|
||||
@@ -10,65 +18,427 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-container">
|
||||
<div class="responsive-column">
|
||||
<h2 class="mat-h2 flex-100">{{ currentProduct.name }}</h2>
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
|
||||
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Details' | translate }}</h3>
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Details' | translate }}</h3>
|
||||
|
||||
<div class="flex-fill layout-row">
|
||||
<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">{{ currentProduct.shortName }}</span>
|
||||
</div>
|
||||
|
||||
@if (currentProduct.description) {
|
||||
<div class="flex-fill layout-row">
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Description' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.description }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-fill">{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
@if (
|
||||
currentProduct.accountType ||
|
||||
currentProduct.customerSegment ||
|
||||
currentProduct.productCode ||
|
||||
currentProduct.effectiveDate ||
|
||||
currentProduct.effectiveTo ||
|
||||
currentProduct.endDate ||
|
||||
currentProduct.introducerRequired !== undefined ||
|
||||
currentProduct.shareLinkingEnabled !== undefined
|
||||
) {
|
||||
<h3 class="mat-h3 flex-100">Product Classification</h3>
|
||||
|
||||
<div class="flex-fill layout-row">
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
@if (currentProduct.accountType) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Account Type:</span>
|
||||
<span class="flex-60">{{ currentProduct.accountType }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.customerSegment) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Customer Segment:</span>
|
||||
<span class="flex-60">{{ currentProduct.customerSegment }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.productCode) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Product Code:</span>
|
||||
<span class="flex-60">{{ currentProduct.productCode }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.effectiveDate) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Effective Date:</span>
|
||||
<span class="flex-60">{{ currentProduct.effectiveDate }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.effectiveTo) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Effective To:</span>
|
||||
<span class="flex-60">{{ currentProduct.effectiveTo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.endDate) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">End Date:</span>
|
||||
<span class="flex-60">{{ currentProduct.endDate }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.introducerRequired !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Introducer Required:</span>
|
||||
<span class="flex-60">{{
|
||||
currentProduct.introducerRequired === true || currentProduct.introducerRequired === 'Yes' ? 'Yes' : 'No'
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.shareLinkingEnabled !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Share Linking Enabled:</span>
|
||||
<span class="flex-60">{{ currentProduct.shareLinkingEnabled | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Currency' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.currency.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-fill layout-row">
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Decimal Places' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.currency.decimalPlaces }}</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">{{ currentProduct.nominalAnnualInterestRate | formatNumber }} %</span>
|
||||
@if (currentProduct.currency.inMultiplesOf !== undefined && currentProduct.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">{{ currentProduct.currency.inMultiplesOf }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-fill layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Interest Compounding Period' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.interestCompoundingPeriodType?.value | translateKey: 'catalogs' }}</span>
|
||||
@if (currentProduct.dormancyAlert !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Dormancy Alert:</span>
|
||||
<span class="flex-60">{{ currentProduct.dormancyAlert | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-fill layout-row">
|
||||
@if (currentProduct.nominationFacility !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Nomination Facility:</span>
|
||||
<span class="flex-60">{{ currentProduct.nominationFacility | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.balanceSwapOption !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Balance Swap Option:</span>
|
||||
<span class="flex-60">{{ currentProduct.balanceSwapOption | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.msme !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">MSME:</span>
|
||||
<span class="flex-60">{{ currentProduct.msme | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.autoDebitFacility !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Auto Debit Facility:</span>
|
||||
<span class="flex-60">{{ currentProduct.autoDebitFacility | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">Interest On OD</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
@if (currentProduct.interestPostingPeriodType?.value) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Interest Posting Period' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.interestPostingPeriodType?.value | translateKey: 'catalogs' }}</span>
|
||||
<span class="flex-60">{{ currentProduct.interestPostingPeriodType.value | translateKey: 'catalogs' }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.interestCalculateSelector) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Interest Calculate:</span>
|
||||
<span class="flex-60">{{ currentProduct.interestCalculateSelector }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.averagePeriod) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Average Period:</span>
|
||||
<span class="flex-60">{{ currentProduct.averagePeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.autoPost !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Auto Post:</span>
|
||||
<span class="flex-60">{{ currentProduct.autoPost | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.frequencyFixDay) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Frequency Fix Day:</span>
|
||||
<span class="flex-60">{{ currentProduct.frequencyFixDay }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.lastInterestDate) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Last Interest Date:</span>
|
||||
<span class="flex-60">{{ currentProduct.lastInterestDate }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Settings' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
@if (currentProduct.minRequiredOpeningBalance) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Minimum Opening Balance' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.minRequiredOpeningBalance | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.lockinPeriodFrequency) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Lock-in Period' | translate }}:</span>
|
||||
<span class="flex-60">{{
|
||||
currentProduct.lockinPeriodFrequency +
|
||||
' ' +
|
||||
(currentProduct.lockinPeriodFrequencyType?.value || currentProduct.lockinPeriodFrequencyType?.code || '')
|
||||
}}</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">{{ currentProduct.withdrawalFeeForTransfers | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (currentProduct.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]="currentProduct.charges">
|
||||
@if (currentProduct.balanceFrequency) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Balance Frequency:</span>
|
||||
<span class="flex-60">{{ currentProduct.balanceFrequency }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Enable Lock-in Period:</span>
|
||||
<span class="flex-60">{{ currentProduct.enableLockinPeriod | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (currentProduct.minBalanceForInterestCalculation) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Balance Required for Interest Calculation' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.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">{{ currentProduct.enforceMinRequiredBalance | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (currentProduct.minRequiredBalance) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Minimum Balance' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.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">{{ currentProduct.withHoldTax | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (currentProduct.withHoldTax) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Tax Group' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.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">{{ currentProduct.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">{{ currentProduct.accountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (currentProduct.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">{{ currentProduct.minOverdraftForInterestCalculation | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.nominalAnnualInterestRateOverdraft) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.nominalAnnualInterestRateOverdraft | formatNumber }} %</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.overdraftLimit) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.overdraftLimit | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.allowOverdraft) {
|
||||
@if (currentProduct.avgBalanceCalcPeriod) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Average Balance Calculation Period:</span>
|
||||
<span class="flex-60">{{ currentProduct.avgBalanceCalcPeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.validForPeriod) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Valid for Period:</span>
|
||||
<span class="flex-60">{{ currentProduct.validForPeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.odAccountClosedZeroBalanceInDayClosing !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">OD Account Closed with Zero Balance in Day Closing:</span>
|
||||
<span class="flex-60">{{ currentProduct.odAccountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ 'labels.inputs.Enable Dormancy Tracking' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.isDormancyTrackingActive | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (currentProduct.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">{{ currentProduct.daysToInactive }}</span>
|
||||
<span class="flex-40">{{ 'labels.inputs.Number of Days to Dormant sub-status' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.daysToDormancy }}</span>
|
||||
<span class="flex-40">{{ 'labels.inputs.Number of Days to Escheat' | translate }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.daysToEscheat }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">Account Facilities</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
@if (currentProduct.accountFacilityConfig) {
|
||||
<div class="layout-row-wrap responsive-column flex-100">
|
||||
@for (feature of accountFacilityFeatureItems; track feature.key) {
|
||||
@if (currentProduct.accountFacilityConfig[feature.key] !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">{{ feature.label }}:</span>
|
||||
<span class="flex-60">{{ currentProduct.accountFacilityConfig[feature.key] | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.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]="currentProduct.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 (currentProduct.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"
|
||||
>{{ currentProduct.depositLimitKyc.general.kycType }} -
|
||||
{{ currentProduct.depositLimitKyc.general.limit }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Minor:</span>
|
||||
<span class="flex-60"
|
||||
>{{ currentProduct.depositLimitKyc.minor.kycType }} - {{ currentProduct.depositLimitKyc.minor.limit }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Small Saving:</span>
|
||||
<span class="flex-60"
|
||||
>{{ currentProduct.depositLimitKyc.smallSaving.kycType }} -
|
||||
{{ currentProduct.depositLimitKyc.smallSaving.limit }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (currentProduct.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]="currentProduct.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>
|
||||
<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>
|
||||
@@ -78,11 +448,15 @@
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.amount | formatNumber }}</td>
|
||||
<td mat-cell *matCellDef="let charge">
|
||||
{{ charge.amount | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="chargeTimeType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Collected On' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let charge">{{ charge.chargeTimeType.value | translateKey: 'catalogs' }}</td>
|
||||
<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>
|
||||
@@ -90,13 +464,165 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-fill">{{ 'labels.heading.Accounting' | translate }}</h3>
|
||||
<mat-divider [inset]="true"></mat-divider>
|
||||
<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]="currentProduct.accountingRule"
|
||||
[accountingMappings]="currentProduct.accountingMappings"
|
||||
></mifosx-view-savings-accounting-details>
|
||||
>
|
||||
</mifosx-view-savings-accounting-details>
|
||||
|
||||
@if (currentProduct.otherGlMappingsMap) {
|
||||
<div class="flex-100 layout-row-wrap responsive-column">
|
||||
<h3 class="mat-h3 flex-100">Other GL Mappings</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
@if (currentProduct.otherGlMappingsMap.assets?.length) {
|
||||
<h4 class="mat-h4 flex-100">Assets</h4>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="currentProduct.otherGlMappingsMap.assets">
|
||||
<ng-container matColumnDef="accountName">
|
||||
<th mat-header-cell *matHeaderCellDef>Account Name</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="glAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.glCode || row.glAccountId }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="['accountName', 'glAccountId']"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glAccountId']"></tr>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (currentProduct.otherGlMappingsMap.liabilities?.length) {
|
||||
<h4 class="mat-h4 flex-100">Liabilities</h4>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="currentProduct.otherGlMappingsMap.liabilities">
|
||||
<ng-container matColumnDef="accountName">
|
||||
<th mat-header-cell *matHeaderCellDef>Account Name</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="glAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.glCode || row.glAccountId }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="['accountName', 'glAccountId']"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glAccountId']"></tr>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (currentProduct.otherGlMappingsMap.expenses?.length) {
|
||||
<h4 class="mat-h4 flex-100">Expenses</h4>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="currentProduct.otherGlMappingsMap.expenses">
|
||||
<ng-container matColumnDef="accountName">
|
||||
<th mat-header-cell *matHeaderCellDef>Account Name</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="glAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.glCode || row.glAccountId }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="['accountName', 'glAccountId']"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glAccountId']"></tr>
|
||||
</table>
|
||||
}
|
||||
|
||||
@if (currentProduct.otherGlMappingsMap.income?.length) {
|
||||
<h4 class="mat-h4 flex-100">Income</h4>
|
||||
<table class="flex-100 mat-elevation-z1" mat-table [dataSource]="currentProduct.otherGlMappingsMap.income">
|
||||
<ng-container matColumnDef="accountName">
|
||||
<th mat-header-cell *matHeaderCellDef>Account Name</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.accountName }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="glAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>GL Account</th>
|
||||
<td mat-cell *matCellDef="let row">{{ row.glCode || row.glAccountId }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="['accountName', 'glAccountId']"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['accountName', 'glAccountId']"></tr>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
currentProduct.paymentChannelToFundSourceMappings?.length ||
|
||||
currentProduct.feeToIncomeAccountMappings?.length ||
|
||||
currentProduct.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 (currentProduct.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]="currentProduct.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 (currentProduct.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]="currentProduct.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 (currentProduct.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]="currentProduct.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>
|
||||
|
||||
+35
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* Copyright since 2025 Mifos Initiative
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute, RouterLink } from '@angular/router';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
@@ -56,6 +63,34 @@ export class CurrentProductGeneralTabComponent {
|
||||
'chargeTimeType'
|
||||
];
|
||||
|
||||
paymentFundSourceDisplayedColumns: string[] = [
|
||||
'paymentTypeId',
|
||||
'fundSourceAccountId'
|
||||
];
|
||||
feesPenaltyIncomeDisplayedColumns: string[] = [
|
||||
'chargeId',
|
||||
'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() {
|
||||
this.route.data.subscribe((data: { currentProduct: any }) => {
|
||||
this.currentProduct = data.currentProduct;
|
||||
|
||||
+5
-4
@@ -18,9 +18,10 @@ $primary: #1353a0;
|
||||
|
||||
.fdp-tabs {
|
||||
background: var(--md-sys-color-surface, #fff); 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 {
|
||||
.mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); }
|
||||
.mat-mdc-tab-header { border-bottom: 1px solid rgb(0 0 0 / 8%); }
|
||||
.mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; }
|
||||
.mat-mdc-tab-body-wrapper { min-height: 200px; }
|
||||
.mat-mdc-tab-body { overflow: visible !important; }
|
||||
@@ -33,7 +34,7 @@ $primary: #1353a0;
|
||||
|
||||
.fdp-action-bar {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-top: 20px; padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
&__cancel { color: rgba(0, 0, 0, 0.6); }
|
||||
margin-top: 20px; padding: 16px 0; border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
&__cancel { color: rgb(0 0 0 / 60%); }
|
||||
&__right { display: flex; gap: 12px; }
|
||||
}
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.cfr-container {
|
||||
padding: 16px 10px;
|
||||
}
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.efr-container {
|
||||
padding: 16px 10px;
|
||||
}
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.vfr-container {
|
||||
padding: 16px 10px;
|
||||
}
|
||||
|
||||
+4
-4
@@ -35,11 +35,11 @@ $primary: #1353a0;
|
||||
.lp-tabs {
|
||||
background: var(--md-sys-color-surface, #fff);
|
||||
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 {
|
||||
.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 {
|
||||
@@ -77,10 +77,10 @@ $primary: #1353a0;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
|
||||
&__cancel {
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
color: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
&__right {
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.lpa-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ $gray-900: #202124;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.lpdi-field {
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ $gray-900: #202124;
|
||||
box-shadow: 0 1px 3px rgba($primary, 0.3) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
flex-direction: column;
|
||||
&__select { max-width: 100%; width: 100%; }
|
||||
}
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ $gray-900: #202124;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.lpc-field {
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ $gray-900: #202124;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.lpd-field {
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ $gray-900: #202124;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.lpir-field {
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -50,7 +50,7 @@
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@
|
||||
.lpt-grid-3 {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -66,11 +66,11 @@
|
||||
.lpt-grid-4 {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
||||
@media (max-width: 900px) {
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ $gray-900: #202124;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.db-field {
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ $gray-900: #202124;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.db-field {
|
||||
|
||||
+2
-2
@@ -31,9 +31,9 @@
|
||||
|
||||
.cr-action-bar {
|
||||
display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px; padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.cr-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
+2
-2
@@ -31,9 +31,9 @@
|
||||
|
||||
.er-action-bar {
|
||||
display: flex; justify-content: flex-end; gap: 12px; margin-top: 20px; padding: 16px 0;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.er-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@
|
||||
|
||||
&:hover {
|
||||
border-color: #0ea5e9;
|
||||
box-shadow: 0 2px 8px rgba(14, 165, 233, 0.12);
|
||||
box-shadow: 0 2px 8px rgb(14 165 233 / 12%);
|
||||
}
|
||||
|
||||
&__icon-wrap {
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ $gray-900: #202124;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.ctc-field {
|
||||
|
||||
+1
-1
@@ -92,7 +92,7 @@ $gray-900: #202124;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.etc-field {
|
||||
|
||||
+3
-3
@@ -81,14 +81,14 @@ $gray-900: #202124;
|
||||
padding: 20px 28px 24px;
|
||||
background: #fff;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.vtc-cell {
|
||||
padding: 14px 16px;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid rgb(0 0 0 / 4%);
|
||||
margin: 3px;
|
||||
transition: background 0.12s;
|
||||
|
||||
@@ -114,7 +114,7 @@ $gray-900: #202124;
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.vtc-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -62,7 +62,7 @@ $gray-900: #0f172a;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (width <= 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ $gray-900: #0f172a;
|
||||
|
||||
&:hover {
|
||||
border-color: #cbd5e1;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 6%);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ $text-muted: #64748b;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.ctg-field {
|
||||
|
||||
@@ -102,7 +102,7 @@ $text-muted: #64748b;
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.etg-field {
|
||||
|
||||
@@ -109,14 +109,14 @@ $text-muted: #64748b;
|
||||
gap: 1px;
|
||||
padding: 20px 28px 24px;
|
||||
|
||||
@media (max-width: 600px) { grid-template-columns: 1fr; }
|
||||
@media (width <= 600px) { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.vtg-cell {
|
||||
padding: 14px 16px;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.04);
|
||||
border: 1px solid rgb(0 0 0 / 4%);
|
||||
margin: 3px;
|
||||
transition: background 0.12s;
|
||||
|
||||
@@ -195,7 +195,7 @@ $text-muted: #64748b;
|
||||
}
|
||||
|
||||
// Responsive
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.vtg-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.cpm-container {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.epm-container {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.vpm-container {
|
||||
padding: 16px 12px;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ $gray-900: #0f172a;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@media (width <= 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -82,7 +82,7 @@ $gray-900: #0f172a;
|
||||
&:hover {
|
||||
border-color: #cbd5e1;
|
||||
background: $gray-50;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 4%);
|
||||
|
||||
.pd-card__arrow {
|
||||
color: $gray-700;
|
||||
@@ -101,62 +101,62 @@ $gray-900: #0f172a;
|
||||
flex-shrink: 0;
|
||||
|
||||
&--blue {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
background: rgb(59 130 246 / 10%);
|
||||
color: #2563eb;
|
||||
}
|
||||
|
||||
&--green {
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
background: rgb(34 197 94 / 10%);
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
&--cyan {
|
||||
background: rgba(6, 182, 212, 0.1);
|
||||
background: rgb(6 182 212 / 10%);
|
||||
color: #0891b2;
|
||||
}
|
||||
|
||||
&--purple {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
background: rgb(139 92 246 / 10%);
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
&--amber {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
background: rgb(245 158 11 / 10%);
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
&--teal {
|
||||
background: rgba(20, 184, 166, 0.1);
|
||||
background: rgb(20 184 166 / 10%);
|
||||
color: #0d9488;
|
||||
}
|
||||
|
||||
&--indigo {
|
||||
background: rgba(99, 102, 241, 0.1);
|
||||
background: rgb(99 102 241 / 10%);
|
||||
color: #4f46e5;
|
||||
}
|
||||
|
||||
&--orange {
|
||||
background: rgba(249, 115, 22, 0.1);
|
||||
background: rgb(249 115 22 / 10%);
|
||||
color: #ea580c;
|
||||
}
|
||||
|
||||
&--slate {
|
||||
background: rgba(100, 116, 139, 0.1);
|
||||
background: rgb(100 116 139 / 10%);
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
&--rose {
|
||||
background: rgba(244, 63, 94, 0.1);
|
||||
background: rgb(244 63 94 / 10%);
|
||||
color: #e11d48;
|
||||
}
|
||||
|
||||
&--emerald {
|
||||
background: rgba(16, 185, 129, 0.1);
|
||||
background: rgb(16 185 129 / 10%);
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
&--red {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
background: rgb(239 68 68 / 10%);
|
||||
color: #dc2626;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-4
@@ -12,7 +12,12 @@
|
||||
<h2 class="rdp-header__title">{{ 'labels.heading.Create Recurring Deposit Product' | translate }}</h2>
|
||||
</div>
|
||||
|
||||
<mat-tab-group class="rdp-tabs" animationDuration="200ms" [preserveContent]="true" [(selectedIndex)]="selectedTabIndex">
|
||||
<mat-tab-group
|
||||
class="rdp-tabs"
|
||||
animationDuration="200ms"
|
||||
[preserveContent]="true"
|
||||
[(selectedIndex)]="selectedTabIndex"
|
||||
>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<i class="fas fa-info-circle rdp-tab-icon"></i>
|
||||
@@ -126,19 +131,19 @@
|
||||
<div class="rdp-action-bar__right">
|
||||
@if (selectedTabIndex > 0) {
|
||||
<button mat-raised-button (click)="selectedTabIndex = selectedTabIndex - 1">
|
||||
<i class="fas fa-arrow-left" style="margin-right: 8px;"></i>
|
||||
<i class="fas fa-arrow-left" style="margin-right: 8px"></i>
|
||||
{{ 'labels.buttons.Previous' | translate }}
|
||||
</button>
|
||||
}
|
||||
@if (selectedTabIndex < totalTabs - 1) {
|
||||
<button mat-raised-button color="primary" (click)="selectedTabIndex = selectedTabIndex + 1">
|
||||
{{ 'labels.buttons.Next' | translate }}
|
||||
<i class="fas fa-arrow-right" style="margin-left: 8px;"></i>
|
||||
<i class="fas fa-arrow-right" style="margin-left: 8px"></i>
|
||||
</button>
|
||||
}
|
||||
@if (recurringDepositProductFormValid && selectedTabIndex === totalTabs - 1) {
|
||||
<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 }}
|
||||
</button>
|
||||
}
|
||||
|
||||
+68
-17
@@ -8,32 +8,83 @@
|
||||
|
||||
$primary: #1353a0;
|
||||
|
||||
.rdp-container { max-width: 960px; margin: 0 auto; padding: 24px 16px; }
|
||||
.rdp-container {
|
||||
max-width: 960px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.rdp-header {
|
||||
display: flex; align-items: center; gap: 10px; margin-bottom: 20px;
|
||||
&__icon { font-size: 20px; color: $primary; }
|
||||
&__title { margin: 0; font-size: 22px; font-weight: 500; }
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
&__icon {
|
||||
font-size: 20px;
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.rdp-tabs {
|
||||
background: var(--md-sys-color-surface, #fff); border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
background: var(--md-sys-color-surface, #fff);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
|
||||
|
||||
::ng-deep {
|
||||
.mat-mdc-tab-header { border-bottom: 1px solid rgba(0, 0, 0, 0.08); }
|
||||
.mat-mdc-tab { min-width: 100px; padding: 0 12px; height: 52px; }
|
||||
.mat-mdc-tab-body-wrapper { min-height: 200px; }
|
||||
.mat-mdc-tab-body { overflow: visible !important; }
|
||||
.mat-mdc-tab-body-content { overflow: visible !important; }
|
||||
.mat-mdc-tab-header {
|
||||
border-bottom: 1px solid rgb(0 0 0 / 8%);
|
||||
}
|
||||
|
||||
.mat-mdc-tab {
|
||||
min-width: 100px;
|
||||
padding: 0 12px;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-wrapper {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rdp-tab-icon { margin-right: 8px; font-size: 14px; }
|
||||
.rdp-tab-content { padding: 24px 16px; }
|
||||
.rdp-tab-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rdp-tab-content {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.rdp-action-bar {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-top: 20px; padding: 16px 0; border-top: 1px solid rgba(0, 0, 0, 0.08);
|
||||
&__cancel { color: rgba(0, 0, 0, 0.6); }
|
||||
&__right { display: flex; gap: 12px; }
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgb(0 0 0 / 8%);
|
||||
|
||||
&__cancel {
|
||||
color: rgb(0 0 0 / 60%);
|
||||
}
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
+85
-245
@@ -7,273 +7,113 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="recurringDepositProductAccountingForm">
|
||||
|
||||
<!-- Accounting Rule -->
|
||||
<div class="rdpa-section">
|
||||
<div class="rdpa-section__header">
|
||||
<i class="fa fa-balance-scale"></i>
|
||||
<h3>{{ 'labels.heading.Accounting Rule' | translate }}</h3>
|
||||
<span class="rdpa-section__icon">
|
||||
<i class="fas fa-book"></i>
|
||||
</span>
|
||||
<h3>Accounting</h3>
|
||||
</div>
|
||||
|
||||
<div class="rdpa-section__body">
|
||||
<mat-radio-group class="rdpa-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 class="rdpa-group">
|
||||
<div class="rdpa-group__header">
|
||||
<h4>Accounting Rule</h4>
|
||||
</div>
|
||||
<div class="rdpa-radio-group">
|
||||
<mat-radio-button [value]="2" [checked]="true" [disabled]="true">Cash</mat-radio-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isCashOrAccrualAccounting()) {
|
||||
|
||||
<!-- Assets -->
|
||||
<div class="rdpa-section">
|
||||
<div class="rdpa-section__header">
|
||||
<i class="fa fa-building"></i>
|
||||
<h3>{{ 'labels.heading.Assets' | translate }}</h3>
|
||||
@for (section of accountingSectionConfigs; track section.key) {
|
||||
<div class="rdpa-group">
|
||||
<div class="rdpa-group__header">
|
||||
<h4>{{ section.title }}</h4>
|
||||
<button type="button" class="rdpa-add-btn" (click)="addOtherGlMapping(section.otherMappingsControlName)">
|
||||
+ Add Other
|
||||
</button>
|
||||
</div>
|
||||
<div class="rdpa-list">
|
||||
@for (row of section.rows; track row.key) {
|
||||
@if (!row.accrualOnly || isAccrualAccounting()) {
|
||||
<div class="rdpa-row">
|
||||
<div class="rdpa-row__meta">
|
||||
<span
|
||||
class="rdpa-pill"
|
||||
[class.rdpa-pill--green]="section.pillClass === 'green'"
|
||||
[class.rdpa-pill--amber]="section.pillClass === 'amber'"
|
||||
[class.rdpa-pill--blue]="section.pillClass === 'blue'"
|
||||
>
|
||||
{{ section.pillLabel }}
|
||||
</span>
|
||||
<span class="rdpa-row__label">{{ row.label }}</span>
|
||||
</div>
|
||||
<div class="rdpa-section__body rdpa-grid">
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.savingsReferenceAccountId"
|
||||
[glAccountList]="assetAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Saving reference'"
|
||||
></mifosx-gl-account-selector>
|
||||
@if (isAccrualAccounting()) {
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.feesReceivableAccountId"
|
||||
[glAccountList]="assetAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Fees Receivable'"
|
||||
></mifosx-gl-account-selector>
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.penaltiesReceivableAccountId"
|
||||
[glAccountList]="assetAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Penalties Receivable'"
|
||||
class="rdpa-row__input"
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.get(row.controlName)"
|
||||
[glAccountList]="row.options"
|
||||
[required]="row.required"
|
||||
[inputLabel]="row.inputLabel"
|
||||
></mifosx-gl-account-selector>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Liabilities -->
|
||||
<div class="rdpa-section">
|
||||
<div class="rdpa-section__header">
|
||||
<i class="fa fa-hand-holding-usd"></i>
|
||||
<h3>{{ 'labels.heading.Liabilities' | translate }}</h3>
|
||||
</div>
|
||||
<div class="rdpa-section__body rdpa-grid">
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.savingsControlAccountId"
|
||||
[glAccountList]="liabilityAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Saving control'"
|
||||
></mifosx-gl-account-selector>
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.transfersInSuspenseAccountId"
|
||||
[glAccountList]="liabilityAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Savings transfers in suspense'"
|
||||
></mifosx-gl-account-selector>
|
||||
@if (isAccrualAccounting()) {
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.interestPayableAccountId"
|
||||
[glAccountList]="liabilityAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Interest Payable'"
|
||||
></mifosx-gl-account-selector>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expenses -->
|
||||
<div class="rdpa-section">
|
||||
<div class="rdpa-section__header">
|
||||
<i class="fa fa-money-bill-wave"></i>
|
||||
<h3>{{ 'labels.heading.Expenses' | translate }}</h3>
|
||||
</div>
|
||||
<div class="rdpa-section__body rdpa-grid">
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.interestOnSavingsAccountId"
|
||||
[glAccountList]="expenseAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Interest on savings'"
|
||||
></mifosx-gl-account-selector>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Income -->
|
||||
<div class="rdpa-section">
|
||||
<div class="rdpa-section__header">
|
||||
<i class="fa fa-chart-pie"></i>
|
||||
<h3>{{ 'labels.heading.Income' | translate }}</h3>
|
||||
</div>
|
||||
<div class="rdpa-section__body rdpa-grid">
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.incomeFromFeeAccountId"
|
||||
[glAccountList]="incomeAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Income from fees'"
|
||||
></mifosx-gl-account-selector>
|
||||
<mifosx-gl-account-selector
|
||||
[inputFormControl]="recurringDepositProductAccountingForm.controls.incomeFromPenaltyAccountId"
|
||||
[glAccountList]="incomeAccountData"
|
||||
[required]="true"
|
||||
[inputLabel]="'Income from penalties'"
|
||||
></mifosx-gl-account-selector>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Accounting Rules -->
|
||||
<div class="rdpa-section">
|
||||
<div class="rdpa-section__header">
|
||||
<i class="fa fa-cogs"></i>
|
||||
<h3>{{ 'labels.heading.Advanced Accounting Rules' | translate }}</h3>
|
||||
<mat-checkbox formControlName="advancedAccountingRules" class="rdpa-header-checkbox"></mat-checkbox>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositProductAccountingForm.value.advancedAccountingRules) {
|
||||
<div class="rdpa-section__body">
|
||||
|
||||
<!-- Payment Fund Sources -->
|
||||
<div class="rdpa-subsection">
|
||||
<div class="rdpa-subsection__header">
|
||||
<h4>{{ 'labels.heading.Configure Fund Sources for Payment Channels' | translate }}</h4>
|
||||
<button type="button" mat-raised-button color="primary" class="rdpa-add-btn"
|
||||
(click)="add('PaymentFundSource', paymentChannelToFundSourceMappings)">
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
@if (paymentChannelToFundSourceMappings.value.length !== 0) {
|
||||
<div class="rdpa-table-wrapper">
|
||||
<table mat-table [dataSource]="paymentChannelToFundSourceMappings.value">
|
||||
<ng-container matColumnDef="paymentTypeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Payment Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let paymentFundSource">
|
||||
{{ paymentFundSource.paymentTypeId | find: paymentTypeData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fundSourceAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Fund Source' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let paymentFundSource">
|
||||
{{ paymentFundSource.fundSourceAccountId | find: assetAccountData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.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>
|
||||
</button>
|
||||
<button mat-icon-button color="warn" (click)="delete(paymentChannelToFundSourceMappings, i)">
|
||||
<fa-icon icon="trash"></fa-icon>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="paymentFundSourceDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: paymentFundSourceDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Fees to Income -->
|
||||
@if (existCharges()) {
|
||||
<div class="rdpa-subsection">
|
||||
<div class="rdpa-subsection__header">
|
||||
<h4>{{ 'labels.heading.Map Fees to Specific Income Accounts' | translate }}</h4>
|
||||
<button type="button" mat-raised-button color="primary" class="rdpa-add-btn"
|
||||
(click)="add('FeesIncome', feeToIncomeAccountMappings)">
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
</button>
|
||||
<div class="rdpa-list" [formArrayName]="section.otherMappingsControlName">
|
||||
@for (
|
||||
otherRow of getOtherGlMappingsArray(section.otherMappingsControlName).controls;
|
||||
track $index;
|
||||
let i = $index
|
||||
) {
|
||||
<div class="rdpa-other-row-wrap">
|
||||
<div class="rdpa-row rdpa-row--other" [formGroupName]="i">
|
||||
<div class="rdpa-row__meta rdpa-row__meta--other">
|
||||
<span
|
||||
class="rdpa-pill"
|
||||
[class.rdpa-pill--green]="section.pillClass === 'green'"
|
||||
[class.rdpa-pill--amber]="section.pillClass === 'amber'"
|
||||
[class.rdpa-pill--blue]="section.pillClass === 'blue'"
|
||||
>
|
||||
{{ section.pillLabel }}
|
||||
</span>
|
||||
<input
|
||||
class="rdpa-row__name rdpa-row__name--other-account"
|
||||
formControlName="accountName"
|
||||
placeholder="Enter account name..."
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
@if (feeToIncomeAccountMappings.value.length !== 0) {
|
||||
<div class="rdpa-table-wrapper">
|
||||
<table mat-table [dataSource]="feeToIncomeAccountMappings.value">
|
||||
<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 -->
|
||||
@if (existCharges()) {
|
||||
<div class="rdpa-subsection">
|
||||
<div class="rdpa-subsection__header">
|
||||
<h4>{{ 'labels.heading.Map Penalties to Specific Income Accounts' | translate }}</h4>
|
||||
<button type="button" mat-raised-button color="primary" class="rdpa-add-btn"
|
||||
(click)="add('PenaltyIncome', penaltyToIncomeAccountMappings)">
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
<div class="rdpa-row__actions rdpa-row__actions--other">
|
||||
<mat-form-field class="rdpa-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="rdpa-remove-btn rdpa-remove-btn--side"
|
||||
(click)="removeOtherGlMapping(section.otherMappingsControlName, i)"
|
||||
>
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
@if (penaltyToIncomeAccountMappings.value.length !== 0) {
|
||||
<div class="rdpa-table-wrapper">
|
||||
<table mat-table [dataSource]="penaltyToIncomeAccountMappings.value">
|
||||
<ng-container matColumnDef="chargeId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Penalty' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let penaltyIncome">
|
||||
{{ penaltyIncome.chargeId | find: penaltyData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incomeAccountId">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Income Account' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let penaltyIncome">
|
||||
{{ penaltyIncome.incomeAccountId | find: incomeAccountData : 'id' : 'name' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.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>
|
||||
|
||||
+264
-39
@@ -6,33 +6,256 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-700: #334155;
|
||||
$gray-900: #202124;
|
||||
|
||||
.rdpa-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
& + & { margin-top: 16px; }
|
||||
|
||||
&__header {
|
||||
.rdpa-section__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i { font-size: 14px; color: #0ea5e9; }
|
||||
h3 { margin: 0; font-size: 14px; font-weight: 600; color: #202124; }
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
&__body { padding: 16px 24px; }
|
||||
.rdpa-section__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
color: #2563eb;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.rdpa-grid {
|
||||
.rdpa-section__body {
|
||||
padding: 16px 24px 20px;
|
||||
}
|
||||
|
||||
.rdpa-group {
|
||||
& + & {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-add-btn {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #2563eb;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.rdpa-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.rdpa-list + .rdpa-list {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.rdpa-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
|
||||
@media (width <= 900px) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-row:has(mifosx-gl-account-selector) {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.rdpa-row--other {
|
||||
border-style: dashed;
|
||||
border-color: #cbd5e1;
|
||||
background: #f8fafc;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
|
||||
@media (width <= 1100px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-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;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-row__meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.rdpa-row__meta--other {
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.rdpa-row__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
.rdpa-row__input {
|
||||
flex: 0 0 22rem;
|
||||
max-width: 22rem;
|
||||
min-width: 18rem;
|
||||
|
||||
@media (width <= 900px) {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-row__input :is(mifosx-gl-account-selector) {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.rdpa-row__input :is(mifosx-gl-account-selector) :is(.layout-column) {
|
||||
width: 100%;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
.rdpa-row__input :is(mifosx-gl-account-selector) :is(mat-form-field) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rdpa-row__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.rdpa-row__actions--other {
|
||||
justify-content: flex-end;
|
||||
align-self: stretch;
|
||||
width: 100%;
|
||||
|
||||
@media (width <= 1100px) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-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%;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-row__name--other-account {
|
||||
max-width: 100%;
|
||||
flex: 1 1 auto;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.rdpa-row__other-field {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.rdpa-row__other-field :is(mat-form-field) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.rdpa-row__other-field :is(.mat-mdc-form-field-required-marker) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rdpa-remove-btn {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #ef4444;
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.rdpa-remove-btn--side {
|
||||
place-self: center end;
|
||||
margin-right: 4px;
|
||||
|
||||
@media (width <= 1100px) {
|
||||
justify-self: start;
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-radio-group {
|
||||
@@ -41,39 +264,41 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.rdpa-header-checkbox { margin-left: auto; }
|
||||
|
||||
.rdpa-subsection {
|
||||
& + & { margin-top: 16px; }
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
.rdpa-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
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;
|
||||
|
||||
h4 { margin: 0; font-size: 13px; font-weight: 600; color: #202124; }
|
||||
&--green {
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
}
|
||||
|
||||
&--amber {
|
||||
background: #fef3c7;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
&--blue {
|
||||
background: #dbeafe;
|
||||
color: #2563eb;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpa-add-btn {
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
@media (width <= 900px) {
|
||||
.rdpa-row__meta {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.rdpa-table-wrapper {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
table { width: 100%; }
|
||||
.rdpa-row__input {
|
||||
flex: 1 1 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.rdpa-grid { grid-template-columns: 1fr; }
|
||||
.rdpa-radio-group { flex-direction: column; }
|
||||
}
|
||||
|
||||
+331
-379
@@ -6,441 +6,393 @@
|
||||
* 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 {
|
||||
UntypedFormGroup,
|
||||
UntypedFormBuilder,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
UntypedFormArray,
|
||||
Validators,
|
||||
UntypedFormBuilder,
|
||||
UntypedFormControl,
|
||||
ReactiveFormsModule
|
||||
UntypedFormGroup,
|
||||
Validators
|
||||
} from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
import { FormDialogComponent } from 'app/shared/form-dialog/form-dialog.component';
|
||||
import { DeleteDialogComponent } from 'app/shared/delete-dialog/delete-dialog.component';
|
||||
|
||||
import { FormfieldBase } from 'app/shared/form-dialog/formfield/model/formfield-base';
|
||||
import { SelectBase } from 'app/shared/form-dialog/formfield/model/select-base';
|
||||
import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
|
||||
import { GlAccountSelectorComponent } from 'app/shared/accounting/gl-account-selector/gl-account-selector.component';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { 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 { GLAccount } from 'app/shared/models/general.model';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
type AccountingSectionKey = 'assets' | 'liabilities' | 'expenses' | 'income';
|
||||
|
||||
type AccountingRowConfig = {
|
||||
key: string;
|
||||
label: string;
|
||||
inputLabel: string;
|
||||
controlName: string;
|
||||
options: GLAccount[];
|
||||
required?: boolean;
|
||||
accrualOnly?: boolean;
|
||||
};
|
||||
|
||||
type AccountingSectionConfig = {
|
||||
key: AccountingSectionKey;
|
||||
title: string;
|
||||
pillLabel: string;
|
||||
pillClass: 'green' | 'amber' | 'blue';
|
||||
rows: AccountingRowConfig[];
|
||||
otherMappingsControlName: string;
|
||||
otherAccountOptions: GLAccount[];
|
||||
};
|
||||
|
||||
type OtherGlMappingValue = {
|
||||
accountName: string;
|
||||
glAccountId: number | null;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-recurring-deposit-product-accounting-step',
|
||||
templateUrl: './recurring-deposit-product-accounting-step.component.html',
|
||||
styleUrls: ['./recurring-deposit-product-accounting-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
MatRadioGroup,
|
||||
MatRadioButton,
|
||||
GlAccountSelectorComponent,
|
||||
MatCheckbox,
|
||||
FaIconComponent,
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
MatHeaderCellDef,
|
||||
MatHeaderCell,
|
||||
MatCellDef,
|
||||
MatCell,
|
||||
MatIconButton,
|
||||
MatHeaderRowDef,
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
FindPipe
|
||||
GlAccountSelectorComponent
|
||||
]
|
||||
})
|
||||
export class RecurringDepositProductAccountingStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dialog = inject(MatDialog);
|
||||
private accounting = inject(Accounting);
|
||||
private translateService = inject(TranslateService);
|
||||
|
||||
@Input() recurringDepositProductsTemplate: any;
|
||||
@Input() accountingRuleData: any;
|
||||
@Input() recurringDepositProductFormValid: boolean;
|
||||
|
||||
recurringDepositProductAccountingForm: UntypedFormGroup;
|
||||
|
||||
chargeData: any;
|
||||
penaltyData: any;
|
||||
paymentTypeData: any;
|
||||
assetAccountData: any;
|
||||
incomeAccountData: any;
|
||||
expenseAccountData: any;
|
||||
liabilityAccountData: any;
|
||||
|
||||
paymentFundSourceDisplayedColumns: string[] = [
|
||||
'paymentTypeId',
|
||||
'fundSourceAccountId',
|
||||
'actions'
|
||||
];
|
||||
feesPenaltyIncomeDisplayedColumns: string[] = [
|
||||
'chargeId',
|
||||
'incomeAccountId',
|
||||
'actions'
|
||||
];
|
||||
assetAccountOptions: GLAccount[] = [];
|
||||
liabilityAccountOptions: GLAccount[] = [];
|
||||
expenseAccountOptions: GLAccount[] = [];
|
||||
incomeAccountOptions: GLAccount[] = [];
|
||||
accountingSectionConfigs: AccountingSectionConfig[] = [];
|
||||
|
||||
constructor() {
|
||||
this.createrecurringDepositProductAccountingForm();
|
||||
this.createRecurringDepositProductAccountingForm();
|
||||
this.setConditionalControls();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.chargeData = this.recurringDepositProductsTemplate.chargeOptions || [];
|
||||
this.penaltyData = this.recurringDepositProductsTemplate.penaltyOptions || [];
|
||||
this.paymentTypeData = this.recurringDepositProductsTemplate.paymentTypeOptions || [];
|
||||
this.assetAccountData = this.recurringDepositProductsTemplate.accountingMappingOptions.assetAccountOptions || [];
|
||||
this.incomeAccountData = this.recurringDepositProductsTemplate.accountingMappingOptions.incomeAccountOptions || [];
|
||||
this.expenseAccountData =
|
||||
this.recurringDepositProductsTemplate.accountingMappingOptions.expenseAccountOptions || [];
|
||||
this.liabilityAccountData =
|
||||
this.recurringDepositProductsTemplate.accountingMappingOptions.liabilityAccountOptions || [];
|
||||
if (!(this.recurringDepositProductsTemplate === undefined) && this.recurringDepositProductsTemplate.id) {
|
||||
this.assignAccountingStepData();
|
||||
}
|
||||
const accountingMappings = this.recurringDepositProductsTemplate?.accountingMappings || {};
|
||||
const mappings = {
|
||||
...accountingMappings,
|
||||
otherGlMappingsMap: this.recurringDepositProductsTemplate?.otherGlMappingsMap
|
||||
};
|
||||
const accountingMappingOptions = this.recurringDepositProductsTemplate?.accountingMappingOptions || {};
|
||||
const accountingRuleId = 2; // Always Cash
|
||||
|
||||
this.assetAccountOptions = accountingMappingOptions.assetAccountOptions || [];
|
||||
this.liabilityAccountOptions = accountingMappingOptions.liabilityAccountOptions || [];
|
||||
this.expenseAccountOptions = accountingMappingOptions.expenseAccountOptions || [];
|
||||
this.incomeAccountOptions = accountingMappingOptions.incomeAccountOptions || [];
|
||||
|
||||
this.buildAccountingSectionConfigs();
|
||||
|
||||
this.recurringDepositProductAccountingForm.patchValue({ accountingRule: accountingRuleId });
|
||||
this.syncAccountingControls(accountingRuleId);
|
||||
this.patchAccountingValues(mappings);
|
||||
}
|
||||
|
||||
assignAccountingStepData() {
|
||||
this.recurringDepositProductAccountingForm.patchValue({
|
||||
accountingRule: this.recurringDepositProductsTemplate.accountingRule.id
|
||||
});
|
||||
if (this.isCashOrAccrualAccounting()) {
|
||||
this.recurringDepositProductAccountingForm.patchValue({
|
||||
savingsReferenceAccountId: this.recurringDepositProductsTemplate.accountingMappings.savingsReferenceAccount.id,
|
||||
savingsControlAccountId: this.recurringDepositProductsTemplate.accountingMappings.savingsControlAccount.id,
|
||||
transfersInSuspenseAccountId:
|
||||
this.recurringDepositProductsTemplate.accountingMappings.transfersInSuspenseAccount.id,
|
||||
incomeFromFeeAccountId: this.recurringDepositProductsTemplate.accountingMappings.incomeFromFeeAccount.id,
|
||||
incomeFromPenaltyAccountId:
|
||||
this.recurringDepositProductsTemplate.accountingMappings.incomeFromPenaltyAccount.id,
|
||||
interestOnSavingsAccountId: this.recurringDepositProductsTemplate.accountingMappings.interestOnSavingsAccount.id
|
||||
});
|
||||
|
||||
if (this.isAccrualAccounting()) {
|
||||
this.recurringDepositProductAccountingForm.patchValue({
|
||||
feesReceivableAccountId: this.recurringDepositProductsTemplate.accountingMappings.feeReceivableAccount.id,
|
||||
penaltiesReceivableAccountId:
|
||||
this.recurringDepositProductsTemplate.accountingMappings.penaltyReceivableAccount.id,
|
||||
interestPayableAccountId: this.recurringDepositProductsTemplate.accountingMappings.interestPayableAccount.id
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
this.recurringDepositProductsTemplate.paymentChannelToFundSourceMappings ||
|
||||
this.recurringDepositProductsTemplate.feeToIncomeAccountMappings ||
|
||||
this.recurringDepositProductsTemplate.penaltyToIncomeAccountMappings
|
||||
) {
|
||||
this.recurringDepositProductAccountingForm.patchValue({
|
||||
advancedAccountingRules: true
|
||||
});
|
||||
}
|
||||
if (this.recurringDepositProductsTemplate.paymentChannelToFundSourceMappings) {
|
||||
this.recurringDepositProductsTemplate.paymentChannelToFundSourceMappings.forEach(
|
||||
(paymentChannelToFundSourceMapping: any) => {
|
||||
const paymentChannelToFundSourceMappingData = this.formBuilder.group({
|
||||
paymentTypeId: [
|
||||
paymentChannelToFundSourceMapping.paymentType.id,
|
||||
Validators.required
|
||||
],
|
||||
fundSourceAccountId: [
|
||||
paymentChannelToFundSourceMapping.fundSourceAccount.id,
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
const formArray = this.recurringDepositProductAccountingForm.controls[
|
||||
'paymentChannelToFundSourceMappings'
|
||||
] as UntypedFormArray;
|
||||
formArray.push(paymentChannelToFundSourceMappingData);
|
||||
}
|
||||
);
|
||||
}
|
||||
if (this.recurringDepositProductsTemplate.feeToIncomeAccountMappings) {
|
||||
this.recurringDepositProductsTemplate.feeToIncomeAccountMappings.forEach((feeToIncomeAccountMapping: any) => {
|
||||
const feeToIncomeAccountMappingData = this.formBuilder.group({
|
||||
chargeId: [
|
||||
feeToIncomeAccountMapping.charge.id,
|
||||
Validators.required
|
||||
],
|
||||
incomeAccountId: [
|
||||
feeToIncomeAccountMapping.incomeAccount.id,
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
const formArray = this.recurringDepositProductAccountingForm.controls[
|
||||
'feeToIncomeAccountMappings'
|
||||
] as UntypedFormArray;
|
||||
formArray.push(feeToIncomeAccountMappingData);
|
||||
});
|
||||
}
|
||||
if (this.recurringDepositProductsTemplate.penaltyToIncomeAccountMappings) {
|
||||
this.recurringDepositProductsTemplate.penaltyToIncomeAccountMappings.forEach(
|
||||
(penaltyToIncomeAccountMapping: any) => {
|
||||
const penaltyToIncomeAccountMappingData = this.formBuilder.group({
|
||||
chargeId: [
|
||||
penaltyToIncomeAccountMapping.charge.id,
|
||||
Validators.required
|
||||
],
|
||||
incomeAccountId: [
|
||||
penaltyToIncomeAccountMapping.incomeAccount.id,
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
const formArray = this.recurringDepositProductAccountingForm.controls[
|
||||
'penaltyToIncomeAccountMappings'
|
||||
] as UntypedFormArray;
|
||||
formArray.push(penaltyToIncomeAccountMappingData);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createrecurringDepositProductAccountingForm() {
|
||||
createRecurringDepositProductAccountingForm() {
|
||||
this.recurringDepositProductAccountingForm = this.formBuilder.group({
|
||||
accountingRule: [1]
|
||||
accountingRule: [2],
|
||||
assetsOtherGlMappings: this.formBuilder.array([]),
|
||||
liabilitiesOtherGlMappings: this.formBuilder.array([]),
|
||||
expensesOtherGlMappings: this.formBuilder.array([]),
|
||||
incomeOtherGlMappings: this.formBuilder.array([])
|
||||
});
|
||||
}
|
||||
|
||||
existCharges(): boolean {
|
||||
return this.chargeData.length > 0;
|
||||
private setConditionalControls() {
|
||||
this.recurringDepositProductAccountingForm.get('accountingRule')?.valueChanges.subscribe((rule: number) => {
|
||||
this.syncAccountingControls(rule);
|
||||
});
|
||||
}
|
||||
|
||||
setConditionalControls() {
|
||||
this.recurringDepositProductAccountingForm.get('accountingRule').valueChanges.subscribe((accountingRule: any) => {
|
||||
if (accountingRule === 2 || accountingRule === 3) {
|
||||
private patchAccountingValues(mappings: any) {
|
||||
this.recurringDepositProductAccountingForm.patchValue({
|
||||
savingsReferenceAccountId: mappings.savingsReferenceAccount?.id || '',
|
||||
savingsControlAccountId: mappings.savingsControlAccount?.id || '',
|
||||
transfersInSuspenseAccountId: mappings.transfersInSuspenseAccount?.id || '',
|
||||
interestOnSavingsAccountId: mappings.interestOnSavingsAccount?.id || '',
|
||||
incomeFromFeeAccountId: mappings.incomeFromFeeAccount?.id || '',
|
||||
incomeFromPenaltyAccountId: mappings.incomeFromPenaltyAccount?.id || '',
|
||||
feesReceivableAccountId: mappings.feeReceivableAccount?.id || '',
|
||||
penaltiesReceivableAccountId: mappings.penaltyReceivableAccount?.id || '',
|
||||
interestPayableAccountId: mappings.interestPayableAccount?.id || ''
|
||||
});
|
||||
|
||||
if (this.isCashOrAccrualAccounting()) {
|
||||
const glMappings = mappings.otherGlMappingsMap || mappings.otherGlMappings;
|
||||
this.setOtherGlMappings('assetsOtherGlMappings', glMappings?.assets || [], this.assetAccountOptions);
|
||||
this.setOtherGlMappings(
|
||||
'liabilitiesOtherGlMappings',
|
||||
glMappings?.liabilities || [],
|
||||
this.liabilityAccountOptions
|
||||
);
|
||||
this.setOtherGlMappings('expensesOtherGlMappings', glMappings?.expenses || [], this.expenseAccountOptions);
|
||||
this.setOtherGlMappings('incomeOtherGlMappings', glMappings?.income || [], this.incomeAccountOptions);
|
||||
}
|
||||
}
|
||||
|
||||
private createOtherGlMappingGroup(otherGlMapping: Partial<OtherGlMappingValue> = {}) {
|
||||
return this.formBuilder.group({
|
||||
accountName: [
|
||||
otherGlMapping.accountName || '',
|
||||
Validators.required
|
||||
],
|
||||
glAccountId: [
|
||||
otherGlMapping.glAccountId || '',
|
||||
Validators.required
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
getOtherGlMappingsArray(controlName: string): UntypedFormArray {
|
||||
return this.recurringDepositProductAccountingForm.get(controlName) as UntypedFormArray;
|
||||
}
|
||||
|
||||
private setOtherGlMappings(controlName: string, otherGlMappings: any[], options: GLAccount[]) {
|
||||
const arr = this.getOtherGlMappingsArray(controlName);
|
||||
arr.clear();
|
||||
(otherGlMappings || []).forEach((m) => {
|
||||
arr.push(
|
||||
this.createOtherGlMappingGroup({
|
||||
accountName: m.accountName,
|
||||
glAccountId: m.glAccountId || options.find((o) => o.glCode === m.glCode)?.id || null
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
addOtherGlMapping(controlName: string) {
|
||||
this.getOtherGlMappingsArray(controlName).push(this.createOtherGlMappingGroup());
|
||||
}
|
||||
|
||||
removeOtherGlMapping(controlName: string, index: number) {
|
||||
this.getOtherGlMappingsArray(controlName).removeAt(index);
|
||||
}
|
||||
|
||||
getOtherGlMappingControl(controlName: string, index: number, fieldName: string): UntypedFormControl {
|
||||
return this.getOtherGlMappingsArray(controlName).at(index).get(fieldName) as UntypedFormControl;
|
||||
}
|
||||
|
||||
private clearOtherGlMappings() {
|
||||
[
|
||||
'assetsOtherGlMappings',
|
||||
'liabilitiesOtherGlMappings',
|
||||
'expensesOtherGlMappings',
|
||||
'incomeOtherGlMappings'
|
||||
].forEach((c) => this.getOtherGlMappingsArray(c).clear());
|
||||
}
|
||||
|
||||
private getOtherGlMappings() {
|
||||
const map = (controlName: string, options: GLAccount[]) =>
|
||||
this.getOtherGlMappingsArray(controlName)
|
||||
.value.filter((item: OtherGlMappingValue) => !!item.accountName || !!item.glAccountId)
|
||||
.map((item: OtherGlMappingValue) => ({
|
||||
accountName: item.accountName,
|
||||
glCode: options.find((o) => o.id === item.glAccountId)?.glCode || ''
|
||||
}))
|
||||
.filter((item: { accountName: string; glCode: string }) => !!item.accountName || !!item.glCode);
|
||||
|
||||
const otherGlMappings = {
|
||||
assets: map('assetsOtherGlMappings', this.assetAccountOptions),
|
||||
liabilities: map('liabilitiesOtherGlMappings', this.liabilityAccountOptions),
|
||||
expenses: map('expensesOtherGlMappings', this.expenseAccountOptions),
|
||||
income: map('incomeOtherGlMappings', this.incomeAccountOptions)
|
||||
};
|
||||
return Object.values(otherGlMappings).some((v) => v.length) ? otherGlMappings : null;
|
||||
}
|
||||
|
||||
private syncAccountingControls(accountingRule: number) {
|
||||
const addControl = (name: string, required = false) => {
|
||||
if (!this.recurringDepositProductAccountingForm.contains(name)) {
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
name,
|
||||
new UntypedFormControl('', required ? Validators.required : undefined)
|
||||
);
|
||||
} else if (required) {
|
||||
this.recurringDepositProductAccountingForm.get(name)?.setValidators(Validators.required);
|
||||
this.recurringDepositProductAccountingForm.get(name)?.updateValueAndValidity({ emitEvent: false });
|
||||
}
|
||||
};
|
||||
const removeControl = (name: string) => {
|
||||
if (this.recurringDepositProductAccountingForm.contains(name)) {
|
||||
this.recurringDepositProductAccountingForm.removeControl(name);
|
||||
}
|
||||
};
|
||||
|
||||
if (this.accounting.isCashOrAccrualAccountingRuleId(accountingRule)) {
|
||||
addControl('savingsReferenceAccountId', true);
|
||||
addControl('savingsControlAccountId', true);
|
||||
addControl('transfersInSuspenseAccountId', true);
|
||||
addControl('interestOnSavingsAccountId', true);
|
||||
addControl('incomeFromFeeAccountId', true);
|
||||
addControl('incomeFromPenaltyAccountId', true);
|
||||
|
||||
if (this.accounting.isAccrualAccountingRuleId(accountingRule)) {
|
||||
addControl('feesReceivableAccountId', true);
|
||||
addControl('penaltiesReceivableAccountId', true);
|
||||
addControl('interestPayableAccountId', true);
|
||||
} else {
|
||||
removeControl('feesReceivableAccountId');
|
||||
removeControl('penaltiesReceivableAccountId');
|
||||
removeControl('interestPayableAccountId');
|
||||
}
|
||||
} else {
|
||||
[
|
||||
'savingsReferenceAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'savingsControlAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'transfersInSuspenseAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'interestOnSavingsAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'incomeFromFeeAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'incomeFromPenaltyAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl('advancedAccountingRules', new UntypedFormControl(false));
|
||||
|
||||
if (accountingRule === 3) {
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'feesReceivableAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'penaltiesReceivableAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'interestPayableAccountId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
}
|
||||
|
||||
this.recurringDepositProductAccountingForm
|
||||
.get('advancedAccountingRules')
|
||||
.valueChanges.subscribe((advancedAccountingRules: boolean) => {
|
||||
if (advancedAccountingRules) {
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'paymentChannelToFundSourceMappings',
|
||||
this.formBuilder.array([])
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'feeToIncomeAccountMappings',
|
||||
this.formBuilder.array([])
|
||||
);
|
||||
this.recurringDepositProductAccountingForm.addControl(
|
||||
'penaltyToIncomeAccountMappings',
|
||||
this.formBuilder.array([])
|
||||
);
|
||||
} else {
|
||||
this.recurringDepositProductAccountingForm.removeControl('paymentChannelToFundSourceMappings');
|
||||
this.recurringDepositProductAccountingForm.removeControl('feeToIncomeAccountMappings');
|
||||
this.recurringDepositProductAccountingForm.removeControl('penaltyToIncomeAccountMappings');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.recurringDepositProductAccountingForm.removeControl('savingsReferenceAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('overdraftPortfolioControlId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('savingsControlAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('transfersInSuspenseAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('interestOnSavingsAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('writeOffAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('incomeFromFeeAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('incomeFromPenaltyAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('incomeFromInterestId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('advancedAccountingRules');
|
||||
this.recurringDepositProductAccountingForm.removeControl('escheatLiabilityId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('feesReceivableAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('penaltiesReceivableAccountId');
|
||||
this.recurringDepositProductAccountingForm.removeControl('interestPayableAccountId');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get paymentChannelToFundSourceMappings(): UntypedFormArray {
|
||||
return this.recurringDepositProductAccountingForm.get('paymentChannelToFundSourceMappings') as UntypedFormArray;
|
||||
}
|
||||
|
||||
get feeToIncomeAccountMappings(): UntypedFormArray {
|
||||
return this.recurringDepositProductAccountingForm.get('feeToIncomeAccountMappings') as UntypedFormArray;
|
||||
}
|
||||
|
||||
get penaltyToIncomeAccountMappings(): UntypedFormArray {
|
||||
return this.recurringDepositProductAccountingForm.get('penaltyToIncomeAccountMappings') as UntypedFormArray;
|
||||
}
|
||||
|
||||
add(formType: string, formArray: UntypedFormArray) {
|
||||
const data = { ...this.getData(formType), pristine: false };
|
||||
const dialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
dialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
formArray.push(response.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
edit(formType: string, formArray: UntypedFormArray, index: number) {
|
||||
const data = { ...this.getData(formType, formArray.at(index).value), layout: { addButtonText: 'Edit' } };
|
||||
const dialogRef = this.dialog.open(FormDialogComponent, { data });
|
||||
dialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.data) {
|
||||
formArray.at(index).patchValue(response.data.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
delete(formArray: UntypedFormArray, index: number) {
|
||||
const dialogRef = this.dialog.open(DeleteDialogComponent, {
|
||||
data: { deleteContext: `this` }
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((response: any) => {
|
||||
if (response.delete) {
|
||||
formArray.removeAt(index);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
};
|
||||
'interestPayableAccountId'
|
||||
].forEach(removeControl);
|
||||
this.clearOtherGlMappings();
|
||||
}
|
||||
}
|
||||
|
||||
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.assetAccountData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.assetAccountData },
|
||||
required: true,
|
||||
order: 2
|
||||
})
|
||||
buildAccountingSectionConfigs() {
|
||||
this.accountingSectionConfigs = [
|
||||
{
|
||||
key: 'assets',
|
||||
title: 'Assets',
|
||||
pillLabel: 'Asset',
|
||||
pillClass: 'green',
|
||||
rows: [
|
||||
{
|
||||
key: 'savingsReferenceAccountId',
|
||||
label: 'Savings Reference Account',
|
||||
inputLabel: 'Savings reference account',
|
||||
controlName: 'savingsReferenceAccountId',
|
||||
options: this.assetAccountOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'feesReceivableAccountId',
|
||||
label: 'Fees Receivable Account',
|
||||
inputLabel: 'Fees receivable account',
|
||||
controlName: 'feesReceivableAccountId',
|
||||
options: this.assetAccountOptions,
|
||||
accrualOnly: true
|
||||
},
|
||||
{
|
||||
key: 'penaltiesReceivableAccountId',
|
||||
label: 'Penalties Receivable Account',
|
||||
inputLabel: 'Penalties receivable account',
|
||||
controlName: 'penaltiesReceivableAccountId',
|
||||
options: this.assetAccountOptions,
|
||||
accrualOnly: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'assetsOtherGlMappings',
|
||||
otherAccountOptions: this.assetAccountOptions
|
||||
},
|
||||
{
|
||||
key: 'liabilities',
|
||||
title: 'Liabilities',
|
||||
pillLabel: 'Liability',
|
||||
pillClass: 'green',
|
||||
rows: [
|
||||
{
|
||||
key: 'savingsControlAccountId',
|
||||
label: 'Savings Control Account',
|
||||
inputLabel: 'Savings control account',
|
||||
controlName: 'savingsControlAccountId',
|
||||
options: this.liabilityAccountOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'transfersInSuspenseAccountId',
|
||||
label: 'Transfers In Suspense Account',
|
||||
inputLabel: 'Transfers in suspense account',
|
||||
controlName: 'transfersInSuspenseAccountId',
|
||||
options: this.liabilityAccountOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'interestPayableAccountId',
|
||||
label: 'Interest Payable Account',
|
||||
inputLabel: 'Interest payable account',
|
||||
controlName: 'interestPayableAccountId',
|
||||
options: this.liabilityAccountOptions,
|
||||
accrualOnly: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'liabilitiesOtherGlMappings',
|
||||
otherAccountOptions: this.liabilityAccountOptions
|
||||
},
|
||||
{
|
||||
key: 'expenses',
|
||||
title: 'Expenses',
|
||||
pillLabel: 'Expense',
|
||||
pillClass: 'amber',
|
||||
rows: [
|
||||
{
|
||||
key: 'interestOnSavingsAccountId',
|
||||
label: 'Interest On Savings Account',
|
||||
inputLabel: 'Interest on savings account',
|
||||
controlName: 'interestOnSavingsAccountId',
|
||||
options: this.expenseAccountOptions,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'expensesOtherGlMappings',
|
||||
otherAccountOptions: this.expenseAccountOptions
|
||||
},
|
||||
{
|
||||
key: 'income',
|
||||
title: 'Income',
|
||||
pillLabel: 'Income',
|
||||
pillClass: 'blue',
|
||||
rows: [
|
||||
{
|
||||
key: 'incomeFromFeeAccountId',
|
||||
label: 'Income From Fees',
|
||||
inputLabel: 'Income from fees',
|
||||
controlName: 'incomeFromFeeAccountId',
|
||||
options: this.incomeAccountOptions,
|
||||
required: true
|
||||
},
|
||||
{
|
||||
key: 'incomeFromPenaltyAccountId',
|
||||
label: 'Income From Penalties',
|
||||
inputLabel: 'Income from penalties',
|
||||
controlName: 'incomeFromPenaltyAccountId',
|
||||
options: this.incomeAccountOptions,
|
||||
required: true
|
||||
}
|
||||
],
|
||||
otherMappingsControlName: 'incomeOtherGlMappings',
|
||||
otherAccountOptions: this.incomeAccountOptions
|
||||
}
|
||||
];
|
||||
return formfields;
|
||||
}
|
||||
|
||||
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: 'Penalty',
|
||||
value: values ? values.chargeId : this.penaltyData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.penaltyData },
|
||||
required: true,
|
||||
order: 1
|
||||
}),
|
||||
new SelectBase({
|
||||
controlName: 'incomeAccountId',
|
||||
label: 'Income Account',
|
||||
value: values ? values.incomeAccountId : this.incomeAccountData[0].id,
|
||||
options: { label: 'name', value: 'id', data: this.incomeAccountData },
|
||||
required: true,
|
||||
order: 2
|
||||
})
|
||||
];
|
||||
return formfields;
|
||||
}
|
||||
|
||||
get recurringDepositProductAccounting() {
|
||||
return this.recurringDepositProductAccountingForm.value;
|
||||
const {
|
||||
assetsOtherGlMappings,
|
||||
liabilitiesOtherGlMappings,
|
||||
expensesOtherGlMappings,
|
||||
incomeOtherGlMappings,
|
||||
...accounting
|
||||
} = this.recurringDepositProductAccountingForm.value;
|
||||
const otherGlMappings = this.getOtherGlMappings();
|
||||
return { ...accounting, ...(otherGlMappings ? { otherGlMappings } : {}) };
|
||||
}
|
||||
|
||||
isCashOrAccrualAccounting(): boolean {
|
||||
|
||||
+8
-1
@@ -23,7 +23,14 @@
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button type="button" mat-raised-button color="primary" class="rdpch-add-btn" (click)="addCharge(charge)" [disabled]="!charge.value">
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="rdpch-add-btn"
|
||||
(click)="addCharge(charge)"
|
||||
[disabled]="!charge.value"
|
||||
>
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add' | translate }}
|
||||
</button>
|
||||
|
||||
+18
-4
@@ -20,8 +20,17 @@
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
|
||||
i { font-size: 14px; color: #0ea5e9; }
|
||||
h3 { margin: 0; font-size: 14px; font-weight: 600; color: #202124; }
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
@@ -35,7 +44,10 @@
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__select { flex: 1; min-width: 200px; }
|
||||
&__select {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpch-add-btn {
|
||||
@@ -51,5 +63,7 @@
|
||||
overflow: hidden;
|
||||
margin-top: 12px;
|
||||
|
||||
table { width: 100%; }
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
+65
-33
@@ -9,9 +9,10 @@
|
||||
<form [formGroup]="recurringDepositProductCurrencyForm">
|
||||
<div class="rdpc-section">
|
||||
<div class="rdpc-section__header">
|
||||
<i class="fa fa-money-bill-wave"></i>
|
||||
<h3>{{ 'labels.inputs.Currency' | translate }}</h3>
|
||||
<i class="fas fa-coins"></i>
|
||||
<span>Currency & Account Status Configuration</span>
|
||||
</div>
|
||||
<div class="rdpc-body">
|
||||
<div class="rdpc-grid">
|
||||
<mat-form-field appearance="outline" class="rdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Currency' | translate }}</mat-label>
|
||||
@@ -26,6 +27,7 @@
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-hint>Default: Indian Rupee - auto-set for domestic products</mat-hint>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Currency' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
@@ -34,46 +36,76 @@
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Decimal Places' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
matInput
|
||||
<mat-select
|
||||
formControlName="digitsAfterDecimal"
|
||||
matTooltip="{{ 'tooltips.Decimal places to be used to track and report' | translate }}"
|
||||
required
|
||||
/>
|
||||
<mat-error *ngIf="recurringDepositProductCurrencyForm.get('digitsAfterDecimal')?.hasError('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-error *ngIf="recurringDepositProductCurrencyForm.get('digitsAfterDecimal')?.hasError('min')">
|
||||
{{ 'labels.inputs.Decimal Places' | translate }} {{ 'labels.commons.Minimum Value must be' | translate }}
|
||||
<strong>0</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<div class="rdpc-field rdpc-field--full rdpc-checkbox-row">
|
||||
<mat-checkbox labelPosition="before" formControlName="setMultiples">
|
||||
{{ 'labels.inputs.Set the recurring deposit installment in multiples of' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositProductCurrencyForm.value.setMultiples) {
|
||||
<mat-form-field appearance="outline" class="rdpc-field">
|
||||
<mat-label>{{ 'labels.inputs.Currency in multiples of' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
matInput
|
||||
formControlName="inMultiplesOf"
|
||||
matTooltip="{{ 'tooltips.Enter multiples of currency value' | translate }}"
|
||||
/>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Currency in multiples of' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
}
|
||||
<div class="rdpc-alert">
|
||||
<div class="rdpc-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="rdpc-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="rdpc-alert">
|
||||
<div class="rdpc-alert__content">
|
||||
<h4>Balance Swap Option</h4>
|
||||
<p>Enable automated transfer of excess funds to linked deposits or vice-versa to maintain liquidity.</p>
|
||||
</div>
|
||||
<mat-radio-group class="rdpc-alert__choices" formControlName="balanceSwapOption">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="rdpc-alert">
|
||||
<div class="rdpc-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Micro, Small and Medium Enterprises</p>
|
||||
</div>
|
||||
<mat-radio-group class="rdpc-alert__choices" formControlName="msme">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="rdpc-alert">
|
||||
<div class="rdpc-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="rdpc-alert__choices" formControlName="nominationFacility">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="rdpc-alert">
|
||||
<div class="rdpc-alert__content">
|
||||
<h4>Auto Debit Facility</h4>
|
||||
<p>Enable automatic deduction of funds for recurring payments, utility bills, or EMI obligations.</p>
|
||||
</div>
|
||||
<mat-radio-group class="rdpc-alert__choices" formControlName="autoDebitFacility">
|
||||
<mat-radio-button [value]="true">yes</mat-radio-button>
|
||||
<mat-radio-button [value]="false">no</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+52
-15
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.rdpc-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -21,46 +27,77 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
span {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rdpc-body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.rdpc-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpc-field {
|
||||
width: 100%;
|
||||
|
||||
&--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpc-checkbox-row {
|
||||
.rdpc-alert {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
padding: 18px 24px 24px;
|
||||
border-top: 1px solid $border;
|
||||
background: #f8fafc;
|
||||
|
||||
@media (width <= 760px) {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.rdpc-grid {
|
||||
grid-template-columns: 1fr;
|
||||
&__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;
|
||||
}
|
||||
}
|
||||
|
||||
+32
-51
@@ -6,12 +6,11 @@
|
||||
* 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 { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatRadioGroup, MatRadioButton } from '@angular/material/radio';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'mifosx-recurring-deposit-product-currency-step',
|
||||
@@ -20,18 +19,23 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatTooltip,
|
||||
MatCheckbox
|
||||
MatRadioGroup,
|
||||
MatRadioButton
|
||||
]
|
||||
})
|
||||
export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
@Input() recurringDepositProductsTemplate: any;
|
||||
|
||||
recurringDepositProductCurrencyForm: UntypedFormGroup;
|
||||
|
||||
currencyData: any;
|
||||
decimalPlacesData = [
|
||||
2,
|
||||
0,
|
||||
3
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createrecurringDepositProductCurrencyForm();
|
||||
@@ -41,24 +45,17 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
this.currencyData = this.recurringDepositProductsTemplate.currencyOptions;
|
||||
|
||||
this.recurringDepositProductCurrencyForm.patchValue({
|
||||
currencyCode: this.recurringDepositProductsTemplate.currency?.code || this.currencyData[0].code,
|
||||
digitsAfterDecimal: this.recurringDepositProductsTemplate.digitsAfterDecimal ?? '',
|
||||
setMultiples: !!this.recurringDepositProductsTemplate.inMultiplesOf,
|
||||
inMultiplesOf: this.recurringDepositProductsTemplate.inMultiplesOf ?? ''
|
||||
currencyCode: this.recurringDepositProductsTemplate.currency?.code || this.currencyData?.[0]?.code || '',
|
||||
digitsAfterDecimal:
|
||||
this.recurringDepositProductsTemplate.digitsAfterDecimal ??
|
||||
this.recurringDepositProductsTemplate.currency?.decimalPlaces ??
|
||||
2,
|
||||
dormancyAlert: this.recurringDepositProductsTemplate.dormancyAlert ?? false,
|
||||
balanceSwapOption: this.recurringDepositProductsTemplate.balanceSwapOption ?? false,
|
||||
msme: this.recurringDepositProductsTemplate.msme ?? false,
|
||||
nominationFacility: this.recurringDepositProductsTemplate.nominationFacility ?? false,
|
||||
autoDebitFacility: this.recurringDepositProductsTemplate.autoDebitFacility ?? false
|
||||
});
|
||||
|
||||
this.setupConditionalValidation();
|
||||
|
||||
// Apply initial validators based on the patched setMultiples value
|
||||
const inMultiplesOfControl = this.recurringDepositProductCurrencyForm.get('inMultiplesOf');
|
||||
const setMultiplesControl = this.recurringDepositProductCurrencyForm.get('setMultiples');
|
||||
if (setMultiplesControl?.value) {
|
||||
inMultiplesOfControl?.setValidators([
|
||||
Validators.required,
|
||||
Validators.min(1)
|
||||
]);
|
||||
inMultiplesOfControl?.updateValueAndValidity();
|
||||
}
|
||||
}
|
||||
|
||||
createrecurringDepositProductCurrencyForm() {
|
||||
@@ -68,46 +65,30 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
Validators.required
|
||||
],
|
||||
digitsAfterDecimal: [
|
||||
'',
|
||||
2,
|
||||
[
|
||||
Validators.required,
|
||||
Validators.min(0)
|
||||
]
|
||||
],
|
||||
setMultiples: [false],
|
||||
inMultiplesOf: ['']
|
||||
});
|
||||
}
|
||||
|
||||
setupConditionalValidation() {
|
||||
const inMultiplesOfControl = this.recurringDepositProductCurrencyForm.get('inMultiplesOf');
|
||||
const setMultiplesControl = this.recurringDepositProductCurrencyForm.get('setMultiples');
|
||||
|
||||
setMultiplesControl?.valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((checked) => {
|
||||
if (checked) {
|
||||
inMultiplesOfControl?.setValidators([
|
||||
Validators.required,
|
||||
Validators.min(1)
|
||||
]);
|
||||
} else {
|
||||
inMultiplesOfControl?.clearValidators();
|
||||
inMultiplesOfControl?.setValue('');
|
||||
}
|
||||
inMultiplesOfControl?.updateValueAndValidity();
|
||||
dormancyAlert: [false],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
nominationFacility: [false],
|
||||
autoDebitFacility: [false]
|
||||
});
|
||||
}
|
||||
|
||||
get recurringDepositProductCurrency() {
|
||||
const formValue = this.recurringDepositProductCurrencyForm.value;
|
||||
const result: any = {
|
||||
return {
|
||||
currencyCode: formValue.currencyCode,
|
||||
digitsAfterDecimal: formValue.digitsAfterDecimal
|
||||
digitsAfterDecimal: formValue.digitsAfterDecimal,
|
||||
dormancyAlert: formValue.dormancyAlert,
|
||||
balanceSwapOption: formValue.balanceSwapOption,
|
||||
msme: formValue.msme,
|
||||
nominationFacility: formValue.nominationFacility,
|
||||
autoDebitFacility: formValue.autoDebitFacility
|
||||
};
|
||||
|
||||
if (formValue.inMultiplesOf !== '' && formValue.inMultiplesOf !== null && formValue.inMultiplesOf !== undefined) {
|
||||
result.inMultiplesOf = formValue.inMultiplesOf;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+61
-7
@@ -10,7 +10,7 @@
|
||||
<div class="rdpd-section">
|
||||
<div class="rdpd-section__header">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
<h3>{{ 'labels.inputs.Product Name' | translate }}</h3>
|
||||
<h3>{{ 'labels.inputs.Product Name' | translate }} & {{ 'labels.inputs.Description' | translate }}</h3>
|
||||
</div>
|
||||
<div class="rdpd-grid">
|
||||
<mat-form-field appearance="outline" class="rdpd-field">
|
||||
@@ -57,13 +57,67 @@
|
||||
cdkTextareaAutosize
|
||||
cdkAutosizeMinRows="2"
|
||||
></textarea>
|
||||
@if (recurringDepositProductDetailsForm.controls.description.hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Description' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rdpd-section">
|
||||
<div class="rdpd-section__header">
|
||||
<i class="fa fa-tags"></i>
|
||||
<h3>Product Classification</h3>
|
||||
</div>
|
||||
|
||||
<div class="rdpd-grid rdpd-grid--classification">
|
||||
<mat-form-field appearance="outline" class="rdpd-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="rdpd-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="rdpd-field">
|
||||
<mat-label>Product Code</mat-label>
|
||||
<input matInput formControlName="productCode" readonly />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpd-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="rdpd-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="rdpd-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="rdpd-field rdpd-field--full rdpd-checkbox-row">
|
||||
<mat-checkbox formControlName="shareLinkingEnabled">Share Linking Enabled</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+31
-9
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.rdpd-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -21,19 +27,19 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +49,22 @@
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpd-grid--classification {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpd-field {
|
||||
@@ -53,8 +75,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.rdpd-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.rdpd-checkbox-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
+80
-12
@@ -6,10 +6,12 @@
|
||||
* 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 { MatTooltip } from '@angular/material/tooltip';
|
||||
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
@@ -22,24 +24,34 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
CdkTextareaAutosize
|
||||
]
|
||||
})
|
||||
export class RecurringDepositProductDetailsStepComponent implements OnInit {
|
||||
export class RecurringDepositProductDetailsStepComponent implements OnInit, OnChanges {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
@Input() recurringDepositProductsTemplate: any;
|
||||
|
||||
recurringDepositProductDetailsForm: UntypedFormGroup;
|
||||
accountTypeData: string[] = [];
|
||||
customerSegmentData: string[] = [];
|
||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
||||
{ value: true, label: 'Yes' },
|
||||
{ value: false, label: 'No' }
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createrecurringDepositProductDetailsForm();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!(this.recurringDepositProductsTemplate === undefined) && this.recurringDepositProductsTemplate.id) {
|
||||
this.recurringDepositProductDetailsForm.patchValue({
|
||||
name: this.recurringDepositProductsTemplate.name,
|
||||
shortName: this.recurringDepositProductsTemplate.shortName,
|
||||
description: this.recurringDepositProductsTemplate.description
|
||||
});
|
||||
if (this.recurringDepositProductsTemplate) {
|
||||
this.applyTemplate(this.recurringDepositProductsTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes.recurringDepositProductsTemplate?.currentValue) {
|
||||
this.applyTemplate(changes.recurringDepositProductsTemplate.currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,14 +65,70 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit {
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
description: [
|
||||
'',
|
||||
description: [''],
|
||||
accountType: [
|
||||
null,
|
||||
Validators.required
|
||||
]
|
||||
],
|
||||
customerSegment: [null],
|
||||
productCode: [''],
|
||||
effectiveTo: [''],
|
||||
endDate: [''],
|
||||
introducerRequired: [false],
|
||||
shareLinkingEnabled: [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 parseDate(dateValue: any): Date | null {
|
||||
if (!dateValue) return null;
|
||||
if (Array.isArray(dateValue)) {
|
||||
return new Date(dateValue[0], dateValue[1] - 1, dateValue[2]);
|
||||
}
|
||||
if (dateValue instanceof Date) {
|
||||
return dateValue;
|
||||
}
|
||||
return new Date(dateValue);
|
||||
}
|
||||
|
||||
private applyTemplate(template: any) {
|
||||
this.accountTypeData = this.getOptionNames(template.accountTypeOptions);
|
||||
this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions);
|
||||
|
||||
const isEditing = !!template.id;
|
||||
this.recurringDepositProductDetailsForm.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 ?? '',
|
||||
effectiveTo: this.parseDate(template.effectiveTo),
|
||||
endDate: this.parseDate(template.endDate),
|
||||
introducerRequired: template.introducerRequired === true || template.introducerRequired === 'Yes',
|
||||
shareLinkingEnabled: !!template.shareLinkingEnabled
|
||||
});
|
||||
}
|
||||
|
||||
get recurringDepositProductDetails() {
|
||||
return this.recurringDepositProductDetailsForm.value;
|
||||
const formValue = { ...this.recurringDepositProductDetailsForm.value };
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
if (formValue.effectiveTo instanceof Date) {
|
||||
formValue.effectiveTo = this.dateUtils.formatDate(formValue.effectiveTo, dateFormat) || '';
|
||||
}
|
||||
if (formValue.endDate instanceof Date) {
|
||||
formValue.endDate = this.dateUtils.formatDate(formValue.endDate, dateFormat) || '';
|
||||
}
|
||||
|
||||
return formValue;
|
||||
}
|
||||
}
|
||||
|
||||
+36
-14
@@ -78,7 +78,13 @@
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpi-field" (click)="endDatePicker.open()">
|
||||
<mat-label>{{ 'labels.inputs.End Date' | translate }}</mat-label>
|
||||
<input matInput [min]="minDate" [max]="maxDate" [matDatepicker]="endDatePicker" formControlName="endDate" />
|
||||
<input
|
||||
matInput
|
||||
[min]="minDate"
|
||||
[max]="maxDate"
|
||||
[matDatepicker]="endDatePicker"
|
||||
formControlName="endDate"
|
||||
/>
|
||||
<mat-datepicker-toggle matSuffix [for]="endDatePicker"></mat-datepicker-toggle>
|
||||
<mat-datepicker #endDatePicker></mat-datepicker>
|
||||
</mat-form-field>
|
||||
@@ -91,7 +97,13 @@
|
||||
</div>
|
||||
|
||||
<div class="rdpi-slab-actions">
|
||||
<button type="button" mat-raised-button color="primary" class="rdpi-add-btn" (click)="addChartSlab(chart.controls.chartSlabs)">
|
||||
<button
|
||||
type="button"
|
||||
mat-raised-button
|
||||
color="primary"
|
||||
class="rdpi-add-btn"
|
||||
(click)="addChartSlab(chart.controls.chartSlabs)"
|
||||
>
|
||||
<fa-icon icon="plus" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Add Slab' | translate }}
|
||||
</button>
|
||||
@@ -105,11 +117,7 @@
|
||||
|
||||
@if (chart.value.chartSlabs.length !== 0) {
|
||||
<div class="rdpi-table-wrapper">
|
||||
<table
|
||||
mat-table
|
||||
[dataSource]="chart.value.chartSlabs"
|
||||
multiTemplateDataRows
|
||||
>
|
||||
<table mat-table [dataSource]="chart.value.chartSlabs" multiTemplateDataRows>
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
@@ -184,7 +192,9 @@
|
||||
>
|
||||
<div
|
||||
class="rdpi-incentives"
|
||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex[chartIndex] ? 'expanded' : 'collapsed'"
|
||||
[@expandChartSlab]="
|
||||
chartSlabIndex === expandChartSlabIndex[chartIndex] ? 'expanded' : 'collapsed'
|
||||
"
|
||||
>
|
||||
<mat-card class="rdpi-incentive-card">
|
||||
<h4 class="rdpi-incentive-title">
|
||||
@@ -205,25 +215,33 @@
|
||||
<div class="rdpi-table-wrapper">
|
||||
<table mat-table [dataSource]="chartSlab.incentives">
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Entity Type' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.entityType | find: entityTypeData : 'id' : 'value' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Attribute Name' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.attributeName | find: attributeNameData : 'id' : 'value' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Condition Type' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.conditionType | find: conditionTypeData : 'id' : 'value' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Attribute Value' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
@switch (incentive.attributeName) {
|
||||
@case (2) {
|
||||
@@ -233,7 +251,9 @@
|
||||
<span>{{ incentive.attributeValue }}</span>
|
||||
}
|
||||
@case (4) {
|
||||
<span>{{ incentive.attributeValue | find: clientTypeData : 'id' : 'name' }}</span>
|
||||
<span>{{
|
||||
incentive.attributeValue | find: clientTypeData : 'id' : 'name'
|
||||
}}</span>
|
||||
}
|
||||
@case (5) {
|
||||
<span>{{
|
||||
@@ -244,7 +264,9 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Incentive Type' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{ incentive.incentiveType | find: incentiveTypeData : 'id' : 'value' }}
|
||||
</td>
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
@media (width <= 600px) {
|
||||
.rdpi-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
+23
-7
@@ -300,7 +300,9 @@
|
||||
>
|
||||
<div
|
||||
class="rdpp-incentives"
|
||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex[chartIndex] ? 'expanded' : 'collapsed'"
|
||||
[@expandChartSlab]="
|
||||
chartSlabIndex === expandChartSlabIndex[chartIndex] ? 'expanded' : 'collapsed'
|
||||
"
|
||||
>
|
||||
<mat-card class="rdpp-incentives__card">
|
||||
<h4>{{ 'labels.heading.Incentives' | translate }}</h4>
|
||||
@@ -319,7 +321,9 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Attribute Name' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{
|
||||
incentive.attributeName
|
||||
@@ -331,7 +335,9 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Condition Type' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{
|
||||
incentive.conditionType
|
||||
@@ -343,13 +349,18 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Attribute Value' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
@switch (incentive.attributeName) {
|
||||
@case (2) {
|
||||
<span>{{
|
||||
incentive.attributeValue
|
||||
| find: recurringDepositProductsTemplate.chartTemplate.genderOptions : 'id' : 'name'
|
||||
| find
|
||||
: recurringDepositProductsTemplate.chartTemplate.genderOptions
|
||||
: 'id'
|
||||
: 'name'
|
||||
}}</span>
|
||||
}
|
||||
@case (3) {
|
||||
@@ -377,7 +388,9 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'labels.inputs.Incentive Type' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let incentive">
|
||||
{{
|
||||
incentive.incentiveType
|
||||
@@ -468,7 +481,10 @@
|
||||
</div>
|
||||
<div class="rdpp-section__body">
|
||||
@if (!isNoneAccounting()) {
|
||||
<mifosx-view-savings-accounting-details [accountingRule]="accountingRule" [accountingMappings]="accountingMappings">
|
||||
<mifosx-view-savings-accounting-details
|
||||
[accountingRule]="accountingRule"
|
||||
[accountingMappings]="accountingMappings"
|
||||
>
|
||||
</mifosx-view-savings-accounting-details>
|
||||
}
|
||||
|
||||
|
||||
+30
-8
@@ -12,7 +12,9 @@
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
|
||||
& + & { margin-top: 16px; }
|
||||
& + & {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
@@ -22,11 +24,22 @@
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
|
||||
i { font-size: 14px; color: #0ea5e9; }
|
||||
h3 { margin: 0; font-size: 14px; font-weight: 600; color: #202124; }
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
}
|
||||
|
||||
&__body { padding: 16px 24px; }
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpp-product-name {
|
||||
@@ -41,7 +54,9 @@
|
||||
padding: 6px 0;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
|
||||
&:last-child { border-bottom: none; }
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpp-label {
|
||||
@@ -63,9 +78,13 @@
|
||||
overflow: hidden;
|
||||
margin-top: 8px;
|
||||
|
||||
table { width: 100%; }
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
& + & { margin-top: 12px; }
|
||||
& + & {
|
||||
margin-top: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpp-sub-heading {
|
||||
@@ -108,6 +127,9 @@
|
||||
table {
|
||||
tr.incentives-row {
|
||||
height: 0;
|
||||
> td { padding: 0; }
|
||||
|
||||
> td {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-168
@@ -7,215 +7,117 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="recurringDepositProductSettingsForm">
|
||||
<!-- Premature option -->
|
||||
<div class="rdps-section">
|
||||
<div class="rdps-section__header">
|
||||
<i class="fa fa-toggle-on"></i>
|
||||
<h3>{{ 'labels.inputs.Deposit Options' | translate }}</h3>
|
||||
<span class="rdps-section__badge"><i class="fas fa-balance-scale"></i></span>
|
||||
<span>Premature option</span>
|
||||
</div>
|
||||
<div class="rdps-grid">
|
||||
<div class="rdps-checkbox-row">
|
||||
<mat-checkbox labelPosition="before" formControlName="isMandatoryDeposit">
|
||||
{{ 'labels.inputs.Is Mandatory Deposit' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="rdps-checkbox-row">
|
||||
<mat-checkbox labelPosition="before" formControlName="adjustAdvanceTowardsFuturePayments">
|
||||
{{ 'labels.inputs.Adjust advance payments toward future installments' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<div class="rdps-checkbox-row">
|
||||
<mat-checkbox labelPosition="before" formControlName="allowWithdrawal">
|
||||
{{ 'labels.inputs.Allow Withdrawals' | translate }}
|
||||
</mat-checkbox>
|
||||
<div class="rdps-body">
|
||||
<div class="rdps-premature-row">
|
||||
<mat-checkbox formControlName="loanFacility">Loan Facility</mat-checkbox>
|
||||
<mat-checkbox formControlName="prematureWithdrawalOption">Premature Withdrawal option</mat-checkbox>
|
||||
<mat-checkbox formControlName="tenureFlexibility">Tenure Flexibility</mat-checkbox>
|
||||
<mat-checkbox formControlName="lienMarked">Lien Marked</mat-checkbox>
|
||||
<mat-checkbox formControlName="withdrawalFeeForTransfers">Apply Withdrawal Fee for Transfers</mat-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lock-in Period -->
|
||||
<div class="rdps-section">
|
||||
<div class="rdps-section__header">
|
||||
<i class="fa fa-lock"></i>
|
||||
<h3>{{ 'labels.inputs.Lock-in Period' | translate }}</h3>
|
||||
<span class="rdps-section__badge"><i class="fas fa-lock"></i></span>
|
||||
<span>{{ 'labels.inputs.Lock-in Period' | translate }}</span>
|
||||
</div>
|
||||
<div class="rdps-grid">
|
||||
<div class="rdps-body">
|
||||
<div class="rdps-lockin-row">
|
||||
<span>Enable Lock-in Period</span>
|
||||
<mat-checkbox formControlName="enableLockinPeriod"></mat-checkbox>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositProductSettingsForm.value.enableLockinPeriod) {
|
||||
<div class="rdps-lockin-fields">
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="lockinPeriodFrequency"
|
||||
matTooltip="{{ 'tooltips.The number at which lock in period occurs' | translate }}"
|
||||
/>
|
||||
<mat-label>Frequency*</mat-label>
|
||||
<input type="number" matInput formControlName="lockinPeriodFrequency" min="1" step="1" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
||||
<mat-label>Type*</mat-label>
|
||||
<mat-select formControlName="lockinPeriodFrequencyType">
|
||||
@for (lockinPeriodFrequencyType of lockinPeriodFrequencyTypeData; track lockinPeriodFrequencyType) {
|
||||
<mat-option [value]="lockinPeriodFrequencyType.id">
|
||||
{{ lockinPeriodFrequencyType.value }}
|
||||
{{ lockinPeriodFrequencyType.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rdps-section">
|
||||
<div class="rdps-section__header">
|
||||
<i class="fa fa-calendar-check"></i>
|
||||
<h3>{{ 'labels.inputs.Minimum Deposit Term' | translate }}</h3>
|
||||
</div>
|
||||
<div class="rdps-grid">
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="minDepositTerm"
|
||||
matTooltip="{{ 'tooltips.The number at which minimum deposit occurs' | translate }}"
|
||||
required
|
||||
/>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Minimum Deposit Term Frequency' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="minDepositTermTypeId" required>
|
||||
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
|
||||
<mat-option [value]="periodFrequencyType.id">
|
||||
{{ periodFrequencyType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Minimum Deposit Term Frequency Type' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Maturity Instructions -->
|
||||
<div class="rdps-section">
|
||||
<div class="rdps-section__header">
|
||||
<i class="fa fa-layer-group"></i>
|
||||
<h3>{{ 'labels.heading.And thereafter, in Multiples of' | translate }}</h3>
|
||||
<span class="rdps-section__badge"><i class="fas fa-lock"></i></span>
|
||||
<span>Maturity Instructions</span>
|
||||
</div>
|
||||
<div class="rdps-body rdps-body--list">
|
||||
<div class="rdps-list-row">
|
||||
<span>Maturity Instructions</span>
|
||||
<mat-checkbox formControlName="maturityInstructions"></mat-checkbox>
|
||||
</div>
|
||||
<div class="rdps-list-row">
|
||||
<span
|
||||
>Option to credit maturity proceeds directly to the linked account or reinvest in another product (like
|
||||
FD).</span
|
||||
>
|
||||
<mat-checkbox formControlName="creditMaturityProceeds"></mat-checkbox>
|
||||
</div>
|
||||
<div class="rdps-list-row">
|
||||
<span>Penalty / Grace Period for Missed Installments</span>
|
||||
<mat-checkbox formControlName="penaltyGracePeriodMissedInstallments"></mat-checkbox>
|
||||
</div>
|
||||
<div class="rdps-list-row">
|
||||
<span>Panel Request Applicable</span>
|
||||
<mat-checkbox formControlName="panelRequestApplicable"></mat-checkbox>
|
||||
</div>
|
||||
<div class="rdps-list-row">
|
||||
<span>No penalty for nominee / legal heir closure</span>
|
||||
<mat-checkbox formControlName="noPenaltyNomineeHeirClosure"></mat-checkbox>
|
||||
</div>
|
||||
<div class="rdps-list-row">
|
||||
<span>Deceased Settlement</span>
|
||||
<mat-checkbox formControlName="deceasedSettlement"></mat-checkbox>
|
||||
</div>
|
||||
<div class="rdps-list-row">
|
||||
<span>Allow Debit Txn after due date</span>
|
||||
<mat-checkbox formControlName="allowDebitTxnAfterDueDate"></mat-checkbox>
|
||||
</div>
|
||||
<div class="rdps-grid">
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="inMultiplesOfDepositTerm"
|
||||
matTooltip="{{ 'tooltips.The number at which multiple deposits occurs' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="inMultiplesOfDepositTermTypeId">
|
||||
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
|
||||
<mat-option [value]="periodFrequencyType.id">
|
||||
{{ periodFrequencyType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Rules -->
|
||||
<div class="rdps-section">
|
||||
<div class="rdps-section__header">
|
||||
<i class="fa fa-calendar-alt"></i>
|
||||
<h3>{{ 'labels.inputs.Maximum Deposit Term' | translate }}</h3>
|
||||
<span class="rdps-section__badge"><i class="fas fa-cog"></i></span>
|
||||
<span>Additional Rules</span>
|
||||
</div>
|
||||
<div class="rdps-grid">
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Frequency' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="maxDepositTerm"
|
||||
matTooltip="{{ 'tooltips.The number at which minimum deposit occurs' | translate }}"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Type' | translate }}</mat-label>
|
||||
<mat-select formControlName="maxDepositTermTypeId">
|
||||
@for (periodFrequencyType of periodFrequencyTypeData; track periodFrequencyType) {
|
||||
<mat-option [value]="periodFrequencyType.id">
|
||||
{{ periodFrequencyType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rdps-section">
|
||||
<div class="rdps-section__header">
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
<h3>{{ 'labels.heading.For Pre-mature closure' | translate }}</h3>
|
||||
</div>
|
||||
<div class="rdps-grid">
|
||||
<div class="rdps-checkbox-row rdps-field--full">
|
||||
<mat-checkbox labelPosition="before" formControlName="preClosurePenalApplicable">
|
||||
{{ 'labels.inputs.Apply Penal Interest (less)' | translate }}
|
||||
<div class="rdps-body">
|
||||
<div class="rdps-layout rdps-layout--additional">
|
||||
<div class="rdps-toggle rdps-toggle--inline">
|
||||
<mat-checkbox formControlName="accountClosedZeroBalanceInDayClosing">
|
||||
<span>Account Closed with Zero balance in Day Closing</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Penal Interest' | translate }} (%)</mat-label>
|
||||
<input type="number" matInput formControlName="preClosurePenalInterest" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdps-field">
|
||||
<mat-label>{{ 'labels.inputs.Period' | translate }}</mat-label>
|
||||
<mat-select formControlName="preClosurePenalInterestOnTypeId">
|
||||
@for (preClosurePenalInterestOnType of preClosurePenalInterestOnTypeData; track preClosurePenalInterestOnType) {
|
||||
<mat-option [value]="preClosurePenalInterestOnType.id">
|
||||
{{ preClosurePenalInterestOnType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-label>Balance Required for Interest Calculation</mat-label>
|
||||
<input type="number" matInput formControlName="minBalanceForInterestCalculation" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rdps-section">
|
||||
<div class="rdps-section__header">
|
||||
<i class="fa fa-file-invoice-dollar"></i>
|
||||
<h3>{{ 'labels.inputs.Tax' | translate }}</h3>
|
||||
</div>
|
||||
<div class="rdps-grid">
|
||||
<div class="rdps-checkbox-row">
|
||||
<mat-checkbox labelPosition="before" formControlName="withHoldTax">
|
||||
{{ 'labels.inputs.Is Withhold Tax Applicable' | translate }}
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
@if (recurringDepositProductSettingsForm.value.withHoldTax) {
|
||||
<mat-form-field appearance="outline" class="rdps-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>
|
||||
</form>
|
||||
|
||||
+114
-15
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.rdps-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -21,28 +27,37 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
span:not(.rdps-section__badge) {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rdps-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 4px 16px;
|
||||
padding: 16px 24px;
|
||||
.rdps-section__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rdps-body {
|
||||
background: #fff;
|
||||
padding: 18px 24px 24px;
|
||||
}
|
||||
|
||||
.rdps-field {
|
||||
@@ -53,14 +68,98 @@
|
||||
}
|
||||
}
|
||||
|
||||
.rdps-checkbox-row {
|
||||
.rdps-premature-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 0;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
mat-checkbox {
|
||||
font-size: 14px;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.rdps-grid {
|
||||
.rdps-layout {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.rdps-layout--additional {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
@media (width <= 1100px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.rdps-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
|
||||
mat-checkbox {
|
||||
font-size: 14px;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&--full {
|
||||
min-height: auto;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.rdps-body--list {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.rdps-list-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
font-size: 14px;
|
||||
color: $gray-900;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
span {
|
||||
max-width: 85%;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.rdps-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;
|
||||
}
|
||||
|
||||
.rdps-lockin-fields {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
|
||||
&--single {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 50%;
|
||||
}
|
||||
|
||||
@media (width <= 640px) {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
&--single {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+42
-88
@@ -7,15 +7,8 @@
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||
import {
|
||||
UntypedFormGroup,
|
||||
UntypedFormBuilder,
|
||||
UntypedFormControl,
|
||||
Validators,
|
||||
ReactiveFormsModule
|
||||
} from '@angular/forms';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@Component({
|
||||
@@ -24,8 +17,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
styleUrls: ['./recurring-deposit-product-settings-step.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
MatCheckbox,
|
||||
MatTooltip
|
||||
MatCheckbox
|
||||
]
|
||||
})
|
||||
export class RecurringDepositProductSettingsStepComponent implements OnInit {
|
||||
@@ -36,106 +28,68 @@ export class RecurringDepositProductSettingsStepComponent implements OnInit {
|
||||
recurringDepositProductSettingsForm: UntypedFormGroup;
|
||||
|
||||
lockinPeriodFrequencyTypeData: any;
|
||||
periodFrequencyTypeData: any;
|
||||
preClosurePenalInterestOnTypeData: any;
|
||||
taxGroupData: any;
|
||||
|
||||
constructor() {
|
||||
this.createrecurringDepositProductSettingsForm();
|
||||
this.setConditionalControls();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.lockinPeriodFrequencyTypeData = this.recurringDepositProductsTemplate.lockinPeriodFrequencyTypeOptions;
|
||||
this.periodFrequencyTypeData = this.recurringDepositProductsTemplate.periodFrequencyTypeOptions.slice(0, -1);
|
||||
this.preClosurePenalInterestOnTypeData = this.recurringDepositProductsTemplate.preClosurePenalInterestOnTypeOptions;
|
||||
this.taxGroupData = this.recurringDepositProductsTemplate.taxGroupOptions;
|
||||
|
||||
if (!(this.recurringDepositProductsTemplate === undefined) && this.recurringDepositProductsTemplate.id) {
|
||||
if (this.recurringDepositProductsTemplate?.id) {
|
||||
this.recurringDepositProductSettingsForm.patchValue({
|
||||
isMandatoryDeposit: this.recurringDepositProductsTemplate.isMandatoryDeposit,
|
||||
adjustAdvanceTowardsFuturePayments: this.recurringDepositProductsTemplate.adjustAdvanceTowardsFuturePayments,
|
||||
allowWithdrawal: this.recurringDepositProductsTemplate.allowWithdrawal,
|
||||
lockinPeriodFrequency: this.recurringDepositProductsTemplate.lockinPeriodFrequency,
|
||||
lockinPeriodFrequencyType: this.recurringDepositProductsTemplate.lockinPeriodFrequencyType
|
||||
? this.recurringDepositProductsTemplate.lockinPeriodFrequencyType.id
|
||||
: '',
|
||||
minDepositTerm: this.recurringDepositProductsTemplate.minDepositTerm,
|
||||
minDepositTermTypeId: this.recurringDepositProductsTemplate.minDepositTermType
|
||||
? this.recurringDepositProductsTemplate.minDepositTermType.id
|
||||
: '',
|
||||
inMultiplesOfDepositTerm: this.recurringDepositProductsTemplate.inMultiplesOfDepositTerm,
|
||||
inMultiplesOfDepositTermTypeId: this.recurringDepositProductsTemplate.inMultiplesOfDepositTermType
|
||||
? this.recurringDepositProductsTemplate.inMultiplesOfDepositTerm.id
|
||||
: '',
|
||||
maxDepositTerm: this.recurringDepositProductsTemplate.maxDepositTerm,
|
||||
maxDepositTermTypeId: this.recurringDepositProductsTemplate.maxDepositTermType
|
||||
? this.recurringDepositProductsTemplate.minDepositTermType.id
|
||||
: '',
|
||||
preClosurePenalApplicable: this.recurringDepositProductsTemplate.preClosurePenalApplicable,
|
||||
preClosurePenalInterest: this.recurringDepositProductsTemplate.preClosurePenalInterest,
|
||||
preClosurePenalInterestOnTypeId: this.recurringDepositProductsTemplate.preClosurePenalInterestOnType
|
||||
? this.recurringDepositProductsTemplate.preClosurePenalInterestOnType.id
|
||||
: '',
|
||||
withHoldTax: this.recurringDepositProductsTemplate.withHoldTax
|
||||
});
|
||||
}
|
||||
|
||||
if (this.recurringDepositProductsTemplate.withHoldTax) {
|
||||
this.recurringDepositProductSettingsForm.patchValue({
|
||||
taxGroupId: this.recurringDepositProductsTemplate.taxGroup
|
||||
? this.recurringDepositProductsTemplate.taxGroup.id
|
||||
: ''
|
||||
loanFacility: this.recurringDepositProductsTemplate.loanFacility ?? false,
|
||||
prematureWithdrawalOption: this.recurringDepositProductsTemplate.prematureWithdrawalOption ?? false,
|
||||
tenureFlexibility: this.recurringDepositProductsTemplate.tenureFlexibility ?? false,
|
||||
lienMarked: this.recurringDepositProductsTemplate.lienMarked ?? false,
|
||||
withdrawalFeeForTransfers: this.recurringDepositProductsTemplate.withdrawalFeeForTransfers ?? false,
|
||||
enableLockinPeriod: this.recurringDepositProductsTemplate.enableLockinPeriod ?? false,
|
||||
lockinPeriodFrequency: this.recurringDepositProductsTemplate.lockinPeriodFrequency ?? '',
|
||||
lockinPeriodFrequencyType: this.recurringDepositProductsTemplate.lockinPeriodFrequencyType?.id ?? '',
|
||||
maturityInstructions: this.recurringDepositProductsTemplate.maturityInstructions ?? false,
|
||||
creditMaturityProceeds: this.recurringDepositProductsTemplate.creditMaturityProceeds ?? false,
|
||||
penaltyGracePeriodMissedInstallments:
|
||||
this.recurringDepositProductsTemplate.penaltyGracePeriodMissedInstallments ?? false,
|
||||
panelRequestApplicable: this.recurringDepositProductsTemplate.panelRequestApplicable ?? false,
|
||||
noPenaltyNomineeHeirClosure: this.recurringDepositProductsTemplate.noPenaltyNomineeHeirClosure ?? false,
|
||||
deceasedSettlement: this.recurringDepositProductsTemplate.deceasedSettlement ?? false,
|
||||
allowDebitTxnAfterDueDate: this.recurringDepositProductsTemplate.allowDebitTxnAfterDueDate ?? false,
|
||||
accountClosedZeroBalanceInDayClosing:
|
||||
this.recurringDepositProductsTemplate.accountClosedZeroBalanceInDayClosing ?? false,
|
||||
minBalanceForInterestCalculation: this.recurringDepositProductsTemplate.minBalanceForInterestCalculation ?? ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
createrecurringDepositProductSettingsForm() {
|
||||
this.recurringDepositProductSettingsForm = this.formBuilder.group({
|
||||
isMandatoryDeposit: [false],
|
||||
adjustAdvanceTowardsFuturePayments: [false],
|
||||
allowWithdrawal: [false],
|
||||
loanFacility: [false],
|
||||
prematureWithdrawalOption: [false],
|
||||
tenureFlexibility: [false],
|
||||
lienMarked: [false],
|
||||
withdrawalFeeForTransfers: [false],
|
||||
enableLockinPeriod: [false],
|
||||
lockinPeriodFrequency: [''],
|
||||
lockinPeriodFrequencyType: [''],
|
||||
minDepositTerm: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
minDepositTermTypeId: [
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
inMultiplesOfDepositTerm: [''],
|
||||
inMultiplesOfDepositTermTypeId: [''],
|
||||
maxDepositTerm: [''],
|
||||
maxDepositTermTypeId: [''],
|
||||
preClosurePenalApplicable: [false],
|
||||
preClosurePenalInterest: [''],
|
||||
preClosurePenalInterestOnTypeId: [''],
|
||||
withHoldTax: [false]
|
||||
});
|
||||
}
|
||||
|
||||
setConditionalControls() {
|
||||
this.recurringDepositProductSettingsForm.get('withHoldTax').valueChanges.subscribe((withHoldTax: any) => {
|
||||
if (withHoldTax) {
|
||||
this.recurringDepositProductSettingsForm.addControl(
|
||||
'taxGroupId',
|
||||
new UntypedFormControl('', Validators.required)
|
||||
);
|
||||
} else {
|
||||
this.recurringDepositProductSettingsForm.removeControl('taxGroupId');
|
||||
}
|
||||
maturityInstructions: [false],
|
||||
creditMaturityProceeds: [false],
|
||||
penaltyGracePeriodMissedInstallments: [false],
|
||||
panelRequestApplicable: [false],
|
||||
noPenaltyNomineeHeirClosure: [false],
|
||||
deceasedSettlement: [false],
|
||||
allowDebitTxnAfterDueDate: [false],
|
||||
accountClosedZeroBalanceInDayClosing: [false],
|
||||
minBalanceForInterestCalculation: ['']
|
||||
});
|
||||
}
|
||||
|
||||
get recurringDepositProductSettings() {
|
||||
const recurringDepositProductSettings = this.recurringDepositProductSettingsForm.value;
|
||||
for (const key in recurringDepositProductSettings) {
|
||||
if (recurringDepositProductSettings[key] === '') {
|
||||
delete recurringDepositProductSettings[key];
|
||||
const settings = { ...this.recurringDepositProductSettingsForm.value };
|
||||
for (const key in settings) {
|
||||
if (settings[key] === '') {
|
||||
delete settings[key];
|
||||
}
|
||||
}
|
||||
return recurringDepositProductSettings;
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
+105
-56
@@ -9,58 +9,99 @@
|
||||
<form [formGroup]="recurringDepositProductTermsForm">
|
||||
<div class="rdpt-section">
|
||||
<div class="rdpt-section__header">
|
||||
<i class="fa fa-coins"></i>
|
||||
<h3>{{ 'labels.inputs.Deposit Amount' | translate }}</h3>
|
||||
<span class="rdpt-section__badge"><i class="fas fa-chart-line"></i></span>
|
||||
<span>Interest Rate & Posting Terms</span>
|
||||
</div>
|
||||
<div class="rdpt-body">
|
||||
<div class="rdpt-banner">
|
||||
<span class="rdpt-banner__icon">ℹ️</span>
|
||||
<span>
|
||||
Recurring Deposit base rate:
|
||||
<b>{{ recurringDepositProductTermsForm.get('nominalAnnualInterestRate').value }}%</b>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="rdpt-grid rdpt-grid--three">
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Minimum' | translate }}</mat-label>
|
||||
<input type="number" min="0" matInput formControlName="minDepositAmount" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Default' | translate }}</mat-label>
|
||||
<input type="number" min="0" matInput formControlName="depositAmount" required />
|
||||
<mat-label>{{ 'labels.inputs.Nominal Annual Interest' | translate }}</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
matTooltip="{{ 'tooltips.The default interest rate set' | translate }}"
|
||||
formControlName="nominalAnnualInterestRate"
|
||||
required
|
||||
min="0"
|
||||
step="0.01"
|
||||
/>
|
||||
@if (recurringDepositProductTermsForm.get('nominalAnnualInterestRate').hasError('required')) {
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Default' | translate }} {{ 'labels.inputs.Deposit Amount' | translate }}
|
||||
{{ 'labels.commons.is' | translate }} <strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
{{ 'labels.inputs.Nominal Annual Interest' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
}
|
||||
@if (recurringDepositProductTermsForm.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="rdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Maximum' | translate }}</mat-label>
|
||||
<input type="number" min="0" matInput formControlName="maxDepositAmount" />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rdpt-section">
|
||||
<div class="rdpt-section__header">
|
||||
<i class="fa fa-percentage"></i>
|
||||
<h3>{{ 'labels.inputs.Interest Compounding Period' | translate }}</h3>
|
||||
</div>
|
||||
<div class="rdpt-grid">
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Interest Compounding Period' | translate }}</mat-label>
|
||||
<mat-select formControlName="interestCompoundingPeriodType" required>
|
||||
@for (interestCompoundingPeriodType of interestCompoundingPeriodTypeData; track interestCompoundingPeriodType) {
|
||||
<mat-option [value]="interestCompoundingPeriodType.id">
|
||||
{{ interestCompoundingPeriodType.value }}
|
||||
<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>
|
||||
{{ 'labels.inputs.Interest Compounding Period' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
{{ 'labels.inputs.Interest Calculation Type' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="rdpt-grid rdpt-grid--three">
|
||||
<mat-form-field appearance="outline" class="rdpt-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 (recurringDepositProductTermsForm.value.interestCalculateSelector === 2) {
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>Average Period</mat-label>
|
||||
<mat-select formControlName="averagePeriod">
|
||||
@for (period of periodFrequencyData; track period.id) {
|
||||
<mat-option [value]="period.value">{{ period.value }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Interest Posting Period' | translate }}</mat-label>
|
||||
<mat-select formControlName="interestPostingPeriodType" required>
|
||||
<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 }}
|
||||
{{ interestPostingPeriodType.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
@@ -69,39 +110,47 @@
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="rdpt-footer">
|
||||
<div class="rdpt-footer__grid">
|
||||
<div class="rdpt-toggle-card">
|
||||
<div>
|
||||
<h4>Auto Interest Post</h4>
|
||||
</div>
|
||||
<div class="rdpt-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="rdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Interest Calculated using' | translate }}</mat-label>
|
||||
<mat-select formControlName="interestCalculationType" required>
|
||||
@for (interestCalculationType of interestCalculationTypeData; track interestCalculationType) {
|
||||
<mat-option [value]="interestCalculationType.id">
|
||||
{{ interestCalculationType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Interest Calculated using' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
<mat-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="rdpt-field">
|
||||
<mat-label>{{ 'labels.inputs.Days in Year' | translate }}</mat-label>
|
||||
<mat-select formControlName="interestCalculationDaysInYearType" required>
|
||||
@for (
|
||||
interestCalculationDaysInYearType of interestCalculationDaysInYearTypeData;
|
||||
track interestCalculationDaysInYearType
|
||||
) {
|
||||
<mat-option [value]="interestCalculationDaysInYearType.id">
|
||||
{{ interestCalculationDaysInYearType.value }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>
|
||||
{{ 'labels.inputs.Days in Year' | translate }} {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
<mat-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 (recurringDepositProductTermsForm.value.interestCalculateSelector === 1) {
|
||||
<div class="rdpt-note">
|
||||
<span>⚠️</span>
|
||||
<span
|
||||
>Daily product-based interest accrual enabled - interest calculated EOD on daily closing balance.</span
|
||||
>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+94
-10
@@ -6,8 +6,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
$accent: #0ea5e9;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-900: #202124;
|
||||
|
||||
.rdpt-section {
|
||||
border: 1px solid #e2e8f0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
@@ -21,23 +27,39 @@
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 24px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid #e8eaed;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
color: #0ea5e9;
|
||||
color: $accent;
|
||||
}
|
||||
|
||||
h3 {
|
||||
h3,
|
||||
span:not(.rdpt-section__badge) {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #202124;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rdpt-section__badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 6px;
|
||||
background: #eff6ff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.rdpt-body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.rdpt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@@ -47,6 +69,14 @@
|
||||
&--three {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
&--three {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rdpt-field {
|
||||
@@ -57,12 +87,66 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.rdpt-grid {
|
||||
grid-template-columns: 1fr;
|
||||
.rdpt-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;
|
||||
|
||||
&--three {
|
||||
&__icon {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpt-footer {
|
||||
padding: 0 24px 24px;
|
||||
border-top: 1px solid #f1f5f9;
|
||||
}
|
||||
|
||||
.rdpt-footer__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
padding-top: 18px;
|
||||
|
||||
@media (width <= 900px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpt-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;
|
||||
}
|
||||
}
|
||||
|
||||
.rdpt-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;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user