feat: update TDS config, RBI reserve returns, current products, and dormancy reports
This commit is contained in:
+1
@@ -50,6 +50,7 @@
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-terms-step
|
||||
[CurrentProductsTemplate]="currentProductsTemplate"
|
||||
[allowOverdraft]="settingsForm.get('allowOverdraft')"
|
||||
></mifosx-current-product-terms-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
+10
-55
@@ -127,15 +127,11 @@ export class CurrentProductAccountingStepComponent implements OnInit {
|
||||
|
||||
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 || '',
|
||||
@@ -267,14 +263,16 @@ export class CurrentProductAccountingStepComponent implements OnInit {
|
||||
};
|
||||
|
||||
if (this.accounting.isCashOrAccrualAccountingRuleId(accountingRule)) {
|
||||
addControl('savingsReferenceAccountId', true);
|
||||
// A current account pays no interest, has no write-off and does not use a transfers suspense account, so
|
||||
// Interest On Savings, Write Off and Transfers In Suspense are not mapped. The keys stay savings-named
|
||||
// because the backend's GL mapping reads them under those names.
|
||||
// No Current Reference either: a current account's cash side posts to the branch cash GL (Financial
|
||||
// Activity: Cash at Main Vault / Teller), the same one the cashier screen uses.
|
||||
removeControl('savingsReferenceAccountId');
|
||||
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)) {
|
||||
@@ -297,12 +295,9 @@ export class CurrentProductAccountingStepComponent implements OnInit {
|
||||
} else {
|
||||
removeControl('savingsReferenceAccountId');
|
||||
removeControl('savingsControlAccountId');
|
||||
removeControl('transfersInSuspenseAccountId');
|
||||
removeControl('interestOnSavingsAccountId');
|
||||
removeControl('incomeFromFeeAccountId');
|
||||
removeControl('incomeFromPenaltyAccountId');
|
||||
removeControl('incomeFromInterestId');
|
||||
removeControl('writeOffAccountId');
|
||||
removeControl('overdraftPortfolioControlId');
|
||||
removeControl('feesReceivableAccountId');
|
||||
removeControl('penaltiesReceivableAccountId');
|
||||
@@ -345,14 +340,6 @@ export class CurrentProductAccountingStepComponent implements OnInit {
|
||||
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',
|
||||
@@ -396,20 +383,12 @@ export class CurrentProductAccountingStepComponent implements OnInit {
|
||||
rows: [
|
||||
{
|
||||
key: 'savingsControlAccountId',
|
||||
label: 'Savings Control Account',
|
||||
inputLabel: 'Savings control account',
|
||||
label: 'Current Control Account',
|
||||
inputLabel: 'Current 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',
|
||||
@@ -430,32 +409,8 @@ export class CurrentProductAccountingStepComponent implements OnInit {
|
||||
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
|
||||
},
|
||||
// No Expenses section: a current account pays no interest and has no write-off, so it maps no expense GL.
|
||||
// The expensesOtherGlMappings control is kept, so an older product's saved expense mappings survive an edit.
|
||||
{
|
||||
key: 'income',
|
||||
title: 'Income',
|
||||
|
||||
+52
-86
@@ -162,29 +162,16 @@
|
||||
}}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Days in Year:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.interestCalculationDaysInYearType }} Days</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 | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Settings' | translate }}</h3>
|
||||
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
@@ -282,39 +269,58 @@
|
||||
</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>
|
||||
}
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Maximum OD Limit:</span>
|
||||
<span class="flex-60">₹ {{ CurrentProduct.overdraftLimit | formatNumber }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.validForPeriod) {
|
||||
@if (CurrentProduct.odMinLimit) {
|
||||
<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>
|
||||
<span class="flex-40">Minimum OD Limit:</span>
|
||||
<span class="flex-60">₹ {{ CurrentProduct.odMinLimit | formatNumber }}</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-40">OD Security Type:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.odSecurityType | titlecase }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Sanction Validity:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.validForPeriod }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Excess Drawing Allowed:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.odExcessAllowed | yesNo }}</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.odExcessAllowed) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Maximum Excess:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.odExcessLimitPercent }} % of limit</span>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Penal Charge on Excess:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.odPenalRate }} % p.a.</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">OD Interest Rate:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.nominalAnnualInterestRateOverdraft }} % p.a.</span>
|
||||
</div>
|
||||
|
||||
@if (CurrentProduct.minOverdraftForInterestCalculation) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Minimum OD for Interest:</span>
|
||||
<span class="flex-60">₹ {{ CurrentProduct.minOverdraftForInterestCalculation | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">End OD Once Fully Repaid:</span>
|
||||
<span class="flex-60">{{ CurrentProduct.odAccountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
@@ -453,15 +459,6 @@
|
||||
|
||||
<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">{{
|
||||
@@ -503,7 +500,7 @@
|
||||
<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__label">Current control account</span>
|
||||
<span class="spa-gl-row__value">{{
|
||||
formatGlAccountById(
|
||||
CurrentProduct.savingsControlAccountId,
|
||||
@@ -511,15 +508,6 @@
|
||||
)
|
||||
}}</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">{{
|
||||
@@ -542,28 +530,6 @@
|
||||
}
|
||||
</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">
|
||||
|
||||
+93
-39
@@ -110,72 +110,126 @@
|
||||
<span>Overdraft (OD) Facility</span>
|
||||
</div>
|
||||
<div class="sps-body">
|
||||
<!-- "Is Overdraft Allowed" is ticked on the Terms tab; this section follows it. -->
|
||||
@if (!CurrentProductSettingsForm.value.allowOverdraft) {
|
||||
<p class="sps-od-check-hint">
|
||||
Overdraft is not allowed on this product. Tick "Is Overdraft Allowed" on the Terms tab to set the OD limits.
|
||||
</p>
|
||||
}
|
||||
<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>
|
||||
<span>End OD Once Fully Repaid (One-time OD only)</span>
|
||||
</mat-checkbox>
|
||||
<p class="sps-od-check-hint">
|
||||
Automatically closes OD accounts when balance reaches zero during day-end closing.
|
||||
At day-end, withdraws the OD sanction when the debit balance is cleared to zero. Only the OD ends, not
|
||||
the account. Leave off for a regular OD: a customer who uses it and repays the same day would lose it.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Right: OD details panel — only visible when OD is allowed -->
|
||||
<!-- Right: the OD scheme every account's sanction is held to — 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>
|
||||
<mat-label>Maximum OD Limit (₹)</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="overdraftLimit"
|
||||
placeholder="e.g. 15"
|
||||
min="0"
|
||||
step="0.01"
|
||||
placeholder="e.g. 500000"
|
||||
min="1"
|
||||
required
|
||||
/>
|
||||
<mat-hint>Highest per account</mat-hint>
|
||||
<mat-error>Enter the maximum OD limit</mat-error>
|
||||
</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-label>Minimum OD Limit (₹)</mat-label>
|
||||
<input type="number" matInput formControlName="odMinLimit" placeholder="e.g. 10000" min="0" />
|
||||
<mat-hint>Lowest per account</mat-hint>
|
||||
@if (CurrentProductSettingsForm.get('odMinLimit')?.hasError('exceedsMax')) {
|
||||
<mat-error>Cannot exceed the maximum OD limit</mat-error>
|
||||
} @else {
|
||||
<mat-error>Cannot be negative</mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="sps-od-radio-group">
|
||||
<span class="sps-od-radio-label">OD Security Type</span>
|
||||
<mat-radio-group formControlName="odSecurityType" class="sps-od-radio-choices">
|
||||
@for (option of odSecurityTypeOptions; track option.value) {
|
||||
<mat-radio-button [value]="option.value">{{ option.label }}</mat-radio-button>
|
||||
}
|
||||
</mat-radio-group>
|
||||
<p class="sps-od-check-hint">{{ selectedSecurityTypeHint }}</p>
|
||||
</div>
|
||||
|
||||
<div class="sps-od-radio-group">
|
||||
<span class="sps-od-radio-label">Sanction Validity</span>
|
||||
<mat-radio-group formControlName="validForPeriod" class="sps-od-radio-choices">
|
||||
@for (option of odValidityOptions; track option.value) {
|
||||
<mat-radio-button [value]="option.value">{{ option.label }}</mat-radio-button>
|
||||
}
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<div class="sps-od-radio-group">
|
||||
<span class="sps-od-radio-label">Excess Drawing (Over Limit)</span>
|
||||
<mat-checkbox formControlName="odExcessAllowed">
|
||||
<span>Allow withdrawal beyond the sanctioned limit</span>
|
||||
</mat-checkbox>
|
||||
@if (!CurrentProductSettingsForm.value.odExcessAllowed) {
|
||||
<p class="sps-od-check-hint">Off: a withdrawal that would cross the limit is refused.</p>
|
||||
} @else {
|
||||
<div class="sps-od-inputs">
|
||||
<mat-form-field appearance="outline" class="sps-field">
|
||||
<mat-label>Maximum Excess (% of limit)</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="odExcessLimitPercent"
|
||||
placeholder="e.g. 10"
|
||||
min="0"
|
||||
step="0.01"
|
||||
required
|
||||
/>
|
||||
<mat-hint>Beyond this, refused</mat-hint>
|
||||
<mat-error>Enter above 0% and up to 25%</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="sps-field">
|
||||
<mat-label>Penal Charge on Excess (% p.a.)</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="odPenalRate"
|
||||
placeholder="e.g. 2"
|
||||
min="0"
|
||||
step="0.01"
|
||||
required
|
||||
/>
|
||||
<mat-hint>Only on the excess amount</mat-hint>
|
||||
<mat-error>Enter above 0% and up to 50%</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
@if (excessExample) {
|
||||
<p class="sps-od-check-hint">{{ excessExample }}</p>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<p class="sps-od-check-hint">
|
||||
Each account is sanctioned its own limit within this range, valid for the period above. The OD interest
|
||||
rate is set on the Terms tab.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
+140
-19
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { AbstractControl, UntypedFormGroup, UntypedFormBuilder, ValidationErrors, 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';
|
||||
@@ -40,6 +40,21 @@ export class CurrentProductSettingsStepComponent implements OnInit {
|
||||
{ id: 'YEARS', value: 'value.years' }
|
||||
];
|
||||
|
||||
/** How long an OD sanction on this product runs before it must be renewed. */
|
||||
odValidityOptions = [
|
||||
{ value: '3M', label: '3 Months' },
|
||||
{ value: '6M', label: '6 Months' },
|
||||
{ value: '1Y', label: '1 Year' },
|
||||
{ value: '2Y', label: '2 Years' },
|
||||
{ value: '3Y', label: '3 Years' }
|
||||
];
|
||||
|
||||
odSecurityTypeOptions = [
|
||||
{ value: 'CLEAN', label: 'Clean', hint: 'No security needed' },
|
||||
{ value: 'SECURED', label: 'Secured', hint: 'Against FD lien, property, etc.' },
|
||||
{ value: 'BOTH', label: 'Both', hint: 'Decided at each sanction' }
|
||||
];
|
||||
|
||||
constructor() {
|
||||
this.createCurrentProductSettingsForm();
|
||||
this.setConditionalControls();
|
||||
@@ -72,14 +87,15 @@ export class CurrentProductSettingsStepComponent implements OnInit {
|
||||
daysToEscheat: isEditing
|
||||
? this.CurrentProductsTemplate.daysToEscheat || DORMANCY_DEFAULTS.daysToEscheat
|
||||
: DORMANCY_DEFAULTS.daysToEscheat,
|
||||
// Overdraft fields
|
||||
// Overdraft scheme (the OD interest rate lives on the Terms tab)
|
||||
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',
|
||||
odMinLimit: isEditing ? this.CurrentProductsTemplate.odMinLimit : '',
|
||||
odSecurityType: isEditing ? this.CurrentProductsTemplate.odSecurityType || 'CLEAN' : 'CLEAN',
|
||||
validForPeriod: this.validityOrDefault(isEditing ? this.CurrentProductsTemplate.validForPeriod : null),
|
||||
odExcessAllowed: isEditing ? !!this.CurrentProductsTemplate.odExcessAllowed : false,
|
||||
odExcessLimitPercent: isEditing ? this.CurrentProductsTemplate.odExcessLimitPercent : '',
|
||||
odPenalRate: isEditing ? this.CurrentProductsTemplate.odPenalRate : '',
|
||||
odAccountClosedZeroBalanceInDayClosing: isEditing
|
||||
? (this.CurrentProductsTemplate.odAccountClosedZeroBalanceInDayClosing ?? false)
|
||||
: false
|
||||
@@ -123,23 +139,123 @@ export class CurrentProductSettingsStepComponent implements OnInit {
|
||||
daysToInactive: [DORMANCY_DEFAULTS.daysToInactive],
|
||||
daysToDormancy: [DORMANCY_DEFAULTS.daysToDormancy],
|
||||
daysToEscheat: [DORMANCY_DEFAULTS.daysToEscheat],
|
||||
// Overdraft (OD) Facility
|
||||
// Overdraft (OD) Facility: the scheme an account's OD sanction is held to
|
||||
allowOverdraft: [false],
|
||||
overdraftLimit: [
|
||||
'',
|
||||
[Validators.min(0)]
|
||||
],
|
||||
nominalAnnualInterestRateOverdraft: [
|
||||
'',
|
||||
[Validators.min(0)]
|
||||
],
|
||||
avgBalanceCalcPeriod: ['1Y'],
|
||||
overdraftLimit: [''],
|
||||
odMinLimit: [''],
|
||||
odSecurityType: ['CLEAN'],
|
||||
validForPeriod: ['1Y'],
|
||||
// Excess drawing past the sanctioned limit: off means it is refused
|
||||
odExcessAllowed: [false],
|
||||
odExcessLimitPercent: [''],
|
||||
odPenalRate: [''],
|
||||
odAccountClosedZeroBalanceInDayClosing: [false]
|
||||
});
|
||||
}
|
||||
|
||||
get selectedSecurityTypeHint(): string {
|
||||
const selected = this.CurrentProductSettingsForm.get('odSecurityType')?.value;
|
||||
return this.odSecurityTypeOptions.find((option) => option.value === selected)?.hint ?? '';
|
||||
}
|
||||
|
||||
/** A worked example on the maximum limit, so the percentages read as rupees. */
|
||||
get excessExample(): string {
|
||||
const value = this.CurrentProductSettingsForm.value;
|
||||
const limit = Number(value.overdraftLimit);
|
||||
const percent = Number(value.odExcessLimitPercent);
|
||||
const penal = Number(value.odPenalRate);
|
||||
if (!limit || !percent) return '';
|
||||
const excess = (limit * percent) / 100;
|
||||
const inr = (n: number) => '₹' + n.toLocaleString('en-IN', { maximumFractionDigits: 2 });
|
||||
let text = `On a ${inr(limit)} limit, up to ${inr(excess)} more may be drawn.`;
|
||||
if (penal) {
|
||||
text += ` If ${inr(excess)} stays over the limit for a day, the penal charge is ${inr((excess * penal) / 100 / 365)}.`;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private validityOrDefault(value: string | null): string {
|
||||
return this.odValidityOptions.some((option) => option.value === value) ? value : '1Y';
|
||||
}
|
||||
|
||||
/** The minimum sanction may not exceed the maximum. */
|
||||
private odMinWithinMax = (control: AbstractControl): ValidationErrors | null => {
|
||||
const max = Number(this.CurrentProductSettingsForm?.get('overdraftLimit')?.value);
|
||||
const min = Number(control.value);
|
||||
if (control.value === '' || control.value === null || !max) return null;
|
||||
return min > max ? { exceedsMax: true } : null;
|
||||
};
|
||||
|
||||
private applyOverdraftValidators(allowed: boolean) {
|
||||
const max = this.CurrentProductSettingsForm.get('overdraftLimit');
|
||||
const min = this.CurrentProductSettingsForm.get('odMinLimit');
|
||||
const security = this.CurrentProductSettingsForm.get('odSecurityType');
|
||||
const validity = this.CurrentProductSettingsForm.get('validForPeriod');
|
||||
if (allowed) {
|
||||
max.setValidators([
|
||||
Validators.required,
|
||||
Validators.min(1)
|
||||
]);
|
||||
min.setValidators([
|
||||
Validators.min(0),
|
||||
this.odMinWithinMax
|
||||
]);
|
||||
security.setValidators([Validators.required]);
|
||||
validity.setValidators([Validators.required]);
|
||||
} else {
|
||||
[
|
||||
max,
|
||||
min,
|
||||
security,
|
||||
validity
|
||||
].forEach((control) => control.clearValidators());
|
||||
}
|
||||
[
|
||||
max,
|
||||
min,
|
||||
security,
|
||||
validity
|
||||
].forEach((control) => control.updateValueAndValidity({ emitEvent: false }));
|
||||
this.applyExcessValidators();
|
||||
}
|
||||
|
||||
/** The excess cap and penal rate are required only while both OD and excess drawing are on. */
|
||||
private applyExcessValidators() {
|
||||
const form = this.CurrentProductSettingsForm;
|
||||
const required = !!form.get('allowOverdraft').value && !!form.get('odExcessAllowed').value;
|
||||
const percent = form.get('odExcessLimitPercent');
|
||||
const penal = form.get('odPenalRate');
|
||||
percent.setValidators(
|
||||
required
|
||||
? [
|
||||
Validators.required,
|
||||
Validators.min(0.01),
|
||||
Validators.max(25)
|
||||
]
|
||||
: []
|
||||
);
|
||||
penal.setValidators(
|
||||
required
|
||||
? [
|
||||
Validators.required,
|
||||
Validators.min(0.01),
|
||||
Validators.max(50)
|
||||
]
|
||||
: []
|
||||
);
|
||||
percent.updateValueAndValidity({ emitEvent: false });
|
||||
penal.updateValueAndValidity({ emitEvent: false });
|
||||
}
|
||||
|
||||
setConditionalControls() {
|
||||
this.CurrentProductSettingsForm.get('allowOverdraft').valueChanges.subscribe((allowed: boolean) =>
|
||||
this.applyOverdraftValidators(!!allowed)
|
||||
);
|
||||
this.CurrentProductSettingsForm.get('odExcessAllowed').valueChanges.subscribe(() => this.applyExcessValidators());
|
||||
this.CurrentProductSettingsForm.get('overdraftLimit').valueChanges.subscribe(() =>
|
||||
this.CurrentProductSettingsForm.get('odMinLimit').updateValueAndValidity({ emitEvent: false })
|
||||
);
|
||||
|
||||
this.CurrentProductSettingsForm.get('enableLockinPeriod').valueChanges.subscribe((enabled: boolean) => {
|
||||
const freq = this.CurrentProductSettingsForm.get('lockinPeriodFrequency');
|
||||
const type = this.CurrentProductSettingsForm.get('lockinPeriodFrequencyType');
|
||||
@@ -168,10 +284,15 @@ export class CurrentProductSettingsStepComponent implements OnInit {
|
||||
|
||||
if (!formValue.allowOverdraft) {
|
||||
delete formValue.overdraftLimit;
|
||||
delete formValue.nominalAnnualInterestRateOverdraft;
|
||||
delete formValue.avgBalanceCalcPeriod;
|
||||
delete formValue.odMinLimit;
|
||||
delete formValue.odSecurityType;
|
||||
delete formValue.validForPeriod;
|
||||
delete formValue.odAccountClosedZeroBalanceInDayClosing;
|
||||
delete formValue.odExcessAllowed;
|
||||
}
|
||||
if (!formValue.allowOverdraft || !formValue.odExcessAllowed) {
|
||||
delete formValue.odExcessLimitPercent;
|
||||
delete formValue.odPenalRate;
|
||||
}
|
||||
|
||||
return formValue;
|
||||
|
||||
+79
-31
@@ -13,20 +13,76 @@
|
||||
<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>
|
||||
@if (allowOverdraftControl) {
|
||||
<div class="spt-toggle-card">
|
||||
<mat-checkbox [formControl]="allowOverdraftControl">
|
||||
<span>Is Overdraft Allowed</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
}
|
||||
@if (!isOverdraftAllowed) {
|
||||
<div class="spt-note">
|
||||
<span>ℹ️</span>
|
||||
<span>Tick "Is Overdraft Allowed" to set the OD interest rate. The OD limits are set on the Settings tab.</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="spt-footer__grid">
|
||||
@if (isOverdraftAllowed) {
|
||||
<mat-form-field appearance="outline" class="spt-field">
|
||||
<mat-label>OD Interest Rate (% p.a.)</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="nominalAnnualInterestRateOverdraft"
|
||||
placeholder="e.g. 12.5"
|
||||
min="0"
|
||||
step="0.01"
|
||||
required
|
||||
/>
|
||||
<mat-hint>Charged on the debit balance</mat-hint>
|
||||
<mat-error>Enter a rate above 0% and up to 50%</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="spt-field">
|
||||
<mat-label>Minimum OD for Interest (₹)</mat-label>
|
||||
<input
|
||||
type="number"
|
||||
matInput
|
||||
formControlName="minOverdraftForInterestCalculation"
|
||||
placeholder="e.g. 100"
|
||||
min="0"
|
||||
/>
|
||||
<mat-hint>No interest while the debit balance is below this</mat-hint>
|
||||
<mat-error>Cannot be negative</mat-error>
|
||||
</mat-form-field>
|
||||
}
|
||||
|
||||
<mat-form-field appearance="outline" class="spt-field">
|
||||
<mat-label>Interest Posting Frequency</mat-label>
|
||||
<mat-select formControlName="interestPostingPeriodType" required>
|
||||
@for (item of interestPostingPeriodTypeData; track item.id) {
|
||||
<mat-option [value]="item.id">
|
||||
{{ item.value | translateKey: 'catalogs' }}
|
||||
</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-hint>Monthly, as RBI requires for advances</mat-hint>
|
||||
<mat-error>
|
||||
Interest Posting Frequency {{ 'labels.commons.is' | translate }}
|
||||
<strong>{{ 'labels.commons.required' | translate }}</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="spt-field">
|
||||
<mat-label>Days in Year</mat-label>
|
||||
<mat-select formControlName="interestCalculationDaysInYearType">
|
||||
@for (item of daysInYearData; track item.id) {
|
||||
<mat-option [value]="item.id">{{ item.label }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="spt-footer">
|
||||
<div class="spt-footer__grid">
|
||||
@@ -43,25 +99,17 @@
|
||||
}
|
||||
</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>
|
||||
@if (isOverdraftAllowed) {
|
||||
<div class="spt-note">
|
||||
<span>ℹ️</span>
|
||||
<span>
|
||||
Rule: daily OD interest = end-of-day debit balance × rate ÷ days in year, applied to the account at the
|
||||
posting frequency.
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+74
-29
@@ -7,12 +7,21 @@
|
||||
*/
|
||||
|
||||
import { Component, OnInit, Input, inject } from '@angular/core';
|
||||
import { UntypedFormGroup, UntypedFormBuilder, Validators } from '@angular/forms';
|
||||
import { AbstractControl, UntypedFormControl, 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';
|
||||
|
||||
/** Posting-period ids an OD's interest may be applied on: RBI requires monthly for advances; quarterly is the outer limit. */
|
||||
const OD_POSTING_PERIOD_IDS = [
|
||||
4,
|
||||
5
|
||||
];
|
||||
|
||||
/**
|
||||
* Interest on OD: the rate and rules an overdraft on this product is charged by. Interest is worked out daily on the
|
||||
* end-of-day debit balance and applied to the account at the posting frequency. The fields apply only when the
|
||||
* Settings tab allows an overdraft, so allowOverdraft (that tab's control) switches their validators on and off.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'mifosx-current-product-terms-step',
|
||||
templateUrl: './current-product-terms-step.component.html',
|
||||
@@ -24,14 +33,18 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
})
|
||||
export class CurrentProductTermsStepComponent implements OnInit {
|
||||
private formBuilder = inject(UntypedFormBuilder);
|
||||
private dateUtils = inject(Dates);
|
||||
private settingsService = inject(SettingsService);
|
||||
|
||||
@Input() CurrentProductsTemplate: any;
|
||||
/** The Settings tab's allowOverdraft control. */
|
||||
@Input() allowOverdraft: AbstractControl;
|
||||
|
||||
CurrentProductTermsForm: UntypedFormGroup;
|
||||
|
||||
interestPostingPeriodTypeData: any[] = [];
|
||||
daysInYearData: Array<{ id: number; label: string }> = [
|
||||
{ id: 365, label: '365 Days (Actual)' },
|
||||
{ id: 360, label: '360 Days' }
|
||||
];
|
||||
yesNoOptions: Array<{ value: boolean; label: string }> = [
|
||||
{ value: true, label: 'Yes' },
|
||||
{ value: false, label: 'No' }
|
||||
@@ -41,29 +54,59 @@ export class CurrentProductTermsStepComponent implements OnInit {
|
||||
this.createCurrentProductTermsForm();
|
||||
}
|
||||
|
||||
get isOverdraftAllowed(): boolean {
|
||||
return !!this.allowOverdraft?.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Settings tab's own control, so the box can be ticked here without going to that tab first; the two
|
||||
* checkboxes are one control and always agree.
|
||||
*/
|
||||
get allowOverdraftControl(): UntypedFormControl | null {
|
||||
return this.allowOverdraft instanceof UntypedFormControl ? this.allowOverdraft : null;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.interestPostingPeriodTypeData = this.CurrentProductsTemplate.interestPostingPeriodTypeOptions || [];
|
||||
const postingOptions: any[] = this.CurrentProductsTemplate.interestPostingPeriodTypeOptions || [];
|
||||
const odPostingOptions = postingOptions.filter((option: any) => OD_POSTING_PERIOD_IDS.includes(option.id));
|
||||
this.interestPostingPeriodTypeData = odPostingOptions.length ? odPostingOptions : postingOptions;
|
||||
this.setYesNoOptions(this.CurrentProductsTemplate?.yesNoOptions);
|
||||
|
||||
const isEditing = !!this.CurrentProductsTemplate.id;
|
||||
const defaultPosting =
|
||||
this.interestPostingPeriodTypeData.find((option: any) => option.id === 4)?.id ??
|
||||
this.interestPostingPeriodTypeData[0]?.id ??
|
||||
'';
|
||||
const storedPosting = this.CurrentProductsTemplate.interestPostingPeriodType?.id;
|
||||
const storedDaysInYear = this.CurrentProductsTemplate.interestCalculationDaysInYearType?.id;
|
||||
const storedPostingOffered = this.interestPostingPeriodTypeData.some((option: any) => option.id === storedPosting);
|
||||
|
||||
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)
|
||||
interestPostingPeriodType: storedPostingOffered ? storedPosting : defaultPosting,
|
||||
interestCalculationDaysInYearType: storedDaysInYear === 360 ? 360 : 365,
|
||||
nominalAnnualInterestRateOverdraft: isEditing ? this.CurrentProductsTemplate.nominalAnnualInterestRateOverdraft : '',
|
||||
minOverdraftForInterestCalculation: isEditing ? this.CurrentProductsTemplate.minOverdraftForInterestCalculation : '',
|
||||
autoPost: isEditing ? (this.CurrentProductsTemplate.autoPost ?? true) : true
|
||||
});
|
||||
|
||||
// The Settings tab patches allowOverdraft in its own ngOnInit, after this one, so follow its changes as well as
|
||||
// its current value.
|
||||
this.applyOverdraftValidators(this.isOverdraftAllowed);
|
||||
this.allowOverdraft?.valueChanges.subscribe((allowed: boolean) => this.applyOverdraftValidators(!!allowed));
|
||||
}
|
||||
|
||||
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 applyOverdraftValidators(allowed: boolean) {
|
||||
const rate = this.CurrentProductTermsForm.get('nominalAnnualInterestRateOverdraft');
|
||||
rate.setValidators(
|
||||
allowed
|
||||
? [
|
||||
Validators.required,
|
||||
Validators.min(0.01),
|
||||
Validators.max(50)
|
||||
]
|
||||
: [Validators.min(0)]
|
||||
);
|
||||
rate.updateValueAndValidity({ emitEvent: false });
|
||||
}
|
||||
|
||||
private setYesNoOptions(options: any[]) {
|
||||
@@ -90,23 +133,25 @@ export class CurrentProductTermsStepComponent implements OnInit {
|
||||
'',
|
||||
Validators.required
|
||||
],
|
||||
autoPost: [true],
|
||||
frequencyFixDay: [
|
||||
30,
|
||||
interestCalculationDaysInYearType: [365],
|
||||
nominalAnnualInterestRateOverdraft: [
|
||||
'',
|
||||
[Validators.min(0)]
|
||||
],
|
||||
lastInterestDate: ['']
|
||||
minOverdraftForInterestCalculation: [
|
||||
'',
|
||||
[Validators.min(0)]
|
||||
],
|
||||
autoPost: [true]
|
||||
});
|
||||
}
|
||||
|
||||
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) || '';
|
||||
if (!this.isOverdraftAllowed) {
|
||||
delete formValue.nominalAnnualInterestRateOverdraft;
|
||||
delete formValue.minOverdraftForInterestCalculation;
|
||||
}
|
||||
|
||||
return { ...formValue };
|
||||
return formValue;
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -50,6 +50,7 @@
|
||||
<div class="cp-tab-content">
|
||||
<mifosx-current-product-terms-step
|
||||
[CurrentProductsTemplate]="currentProductAndTemplate"
|
||||
[allowOverdraft]="settingsForm.get('allowOverdraft')"
|
||||
></mifosx-current-product-terms-step>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
+59
-21
@@ -257,16 +257,10 @@
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentProduct.frequencyFixDay) {
|
||||
@if (currentProduct.interestCalculationDaysInYearType?.id) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Frequency Fix Day</span>
|
||||
<span class="vsp-v">{{ currentProduct.frequencyFixDay }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentProduct.lastInterestDate) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Last Interest Date</span>
|
||||
<span class="vsp-v">{{ currentProduct.lastInterestDate | dateFormat }}</span>
|
||||
<span class="vsp-k">Days in Year</span>
|
||||
<span class="vsp-v">{{ currentProduct.interestCalculationDaysInYearType.id }} Days</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -396,37 +390,57 @@
|
||||
<span class="vsp-k">{{
|
||||
'labels.inputs.Minimum Overdraft Required for Interest Calculation' | translate
|
||||
}}</span>
|
||||
<span class="vsp-v">{{ currentProduct.minOverdraftForInterestCalculation | formatNumber }}</span>
|
||||
<span class="vsp-v">₹ {{ currentProduct.minOverdraftForInterestCalculation | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentProduct.nominalAnnualInterestRateOverdraft) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">{{ 'labels.inputs.Nominal Annual Interest for Overdraft' | translate }}</span>
|
||||
<span class="vsp-v">{{ currentProduct.nominalAnnualInterestRateOverdraft | formatNumber }} %</span>
|
||||
<span class="vsp-v">{{ currentProduct.nominalAnnualInterestRateOverdraft | formatNumber }} % p.a.</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentProduct.overdraftLimit) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">{{ 'labels.inputs.Maximum Overdraft Amount Limit' | translate }}</span>
|
||||
<span class="vsp-v">{{ currentProduct.overdraftLimit | formatNumber }}</span>
|
||||
<span class="vsp-v">₹ {{ currentProduct.overdraftLimit | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentProduct.allowOverdraft) {
|
||||
@if (currentProduct.avgBalanceCalcPeriod) {
|
||||
@if (currentProduct.odMinLimit) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Average Balance Calculation Period</span>
|
||||
<span class="vsp-v">{{ currentProduct.avgBalanceCalcPeriod }}</span>
|
||||
<span class="vsp-k">Minimum OD Limit</span>
|
||||
<span class="vsp-v">₹ {{ currentProduct.odMinLimit | formatNumber }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentProduct.odSecurityType) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">OD Security Type</span>
|
||||
<span class="vsp-v">{{ currentProduct.odSecurityType | titlecase }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentProduct.validForPeriod) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Valid for Period</span>
|
||||
<span class="vsp-k">Sanction Validity</span>
|
||||
<span class="vsp-v">{{ currentProduct.validForPeriod }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Excess Drawing Allowed</span>
|
||||
<span class="vsp-v">{{ currentProduct.odExcessAllowed | yesNo }}</span>
|
||||
</div>
|
||||
@if (currentProduct.odExcessAllowed) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Maximum Excess</span>
|
||||
<span class="vsp-v">{{ currentProduct.odExcessLimitPercent | formatNumber }} % of limit</span>
|
||||
</div>
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Penal Charge on Excess</span>
|
||||
<span class="vsp-v">{{ currentProduct.odPenalRate | formatNumber }} % p.a.</span>
|
||||
</div>
|
||||
}
|
||||
@if (currentProduct.odAccountClosedZeroBalanceInDayClosing !== undefined) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">OD Account Closed with Zero Balance in Day Closing</span>
|
||||
<span class="vsp-k">End OD Once Fully Repaid</span>
|
||||
<span class="vsp-v">{{ currentProduct.odAccountClosedZeroBalanceInDayClosing | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
@@ -588,10 +602,34 @@
|
||||
<div class="vsp-section-ic vsp-section-ic--green"><fa-icon icon="book"></fa-icon></div>
|
||||
<h3>{{ 'labels.heading.Accounting' | translate }}</h3>
|
||||
</div>
|
||||
<mifosx-view-savings-accounting-details
|
||||
[accountingRule]="currentProduct.accountingRule"
|
||||
[accountingMappings]="currentProduct.accountingMappings"
|
||||
></mifosx-view-savings-accounting-details>
|
||||
<!--
|
||||
The current product's own mappings only, under current-account names. The shared savings view is not used:
|
||||
it labels everything as savings and lists savings-only GLs (Saving reference, Interest on savings...), and
|
||||
while current and savings products share mapping rows by id it would show a savings product's GLs here.
|
||||
There is no cash (reference) GL: a current account's cash side posts to the branch cash GL.
|
||||
-->
|
||||
<div class="vsp-grid">
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Type</span>
|
||||
<span class="vsp-v">{{ currentProduct.accountingRule?.value || 'Cash' }}</span>
|
||||
</div>
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">Cash GL (deposit / withdrawal)</span>
|
||||
<span class="vsp-v">Branch cash GL — Financial Activity: Cash at Main Vault / Teller</span>
|
||||
</div>
|
||||
@for (gl of currentAccountingRows; track gl.label) {
|
||||
<div class="vsp-kv">
|
||||
<span class="vsp-k">{{ gl.label }}</span>
|
||||
<span class="vsp-v">
|
||||
@if (gl.account) {
|
||||
({{ gl.account.glCode }}) {{ gl.account.name }}
|
||||
} @else {
|
||||
—
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- OTHER GL MAPPINGS -->
|
||||
|
||||
+16
-2
@@ -22,7 +22,6 @@ import {
|
||||
MatRowDef,
|
||||
MatRow
|
||||
} from '@angular/material/table';
|
||||
import { ViewSavingsAccountingDetailsComponent } from '../../../../shared/accounting/view-savings-accounting-details/view-savings-accounting-details.component';
|
||||
import { DateFormatPipe } from '../../../../pipes/date-format.pipe';
|
||||
import { FormatNumberPipe } from '../../../../pipes/format-number.pipe';
|
||||
import { YesnoPipe } from '../../../../pipes/yesno.pipe';
|
||||
@@ -47,7 +46,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatHeaderRow,
|
||||
MatRowDef,
|
||||
MatRow,
|
||||
ViewSavingsAccountingDetailsComponent,
|
||||
DateFormatPipe,
|
||||
FormatNumberPipe,
|
||||
YesnoPipe
|
||||
@@ -102,4 +100,20 @@ export class CurrentProductGeneralTabComponent {
|
||||
isCashOrAccrualAccounting(): boolean {
|
||||
return this.accounting.isCashOrAccrualAccounting(this.currentProduct.accountingRule);
|
||||
}
|
||||
|
||||
/** The GLs a current product maps, by the names the Accounting tab gives them. */
|
||||
get currentAccountingRows(): Array<{ label: string; account: any }> {
|
||||
const m = this.currentProduct?.accountingMappings || {};
|
||||
const rows = [
|
||||
{ label: 'Current Control (Liability)', account: m.savingsControlAccount },
|
||||
{ label: 'Overdraft Portfolio Control (Asset)', account: m.overdraftPortfolioControl },
|
||||
{ label: 'Income From Fees', account: m.incomeFromFeeAccount },
|
||||
{ label: 'Income From Penalties', account: m.incomeFromPenaltyAccount },
|
||||
{ label: 'Income From Interest', account: m.incomeFromInterest }
|
||||
];
|
||||
if (this.currentProduct?.isDormancyTrackingActive) {
|
||||
rows.push({ label: 'Escheat Liability', account: m.escheatLiabilityAccount });
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user