rd account and rd product code update
This commit is contained in:
@@ -108,7 +108,7 @@ export class CkycReportsComponent implements OnInit, AfterViewInit {
|
||||
|
||||
ngOnInit(): void {
|
||||
this.parseAndStore(SAMPLE_CKYC_FILE);
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
console.log(
|
||||
'[CKYC] Sample loaded —',
|
||||
this.customers.length,
|
||||
|
||||
+5
-2
@@ -74,7 +74,8 @@
|
||||
<mifosx-recurring-deposits-account-interest-rate-chart-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[rdInterestRates]="rdInterestRates"
|
||||
[submittedOnDate]="submittedOnDate"
|
||||
(tenureSelected)="handleTenureSelected($event)"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-interest-rate-chart-step>
|
||||
|
||||
@@ -97,11 +98,13 @@
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[recurringDepositAccountData]="recurringDepositAccountData"
|
||||
[rdInterestRates]="rdInterestRates"
|
||||
[rdNominee]="rdNominee"
|
||||
[rdCurrencyFlags]="rdCurrencyFlags"
|
||||
[rdTenureDays]="rdTenureDays"
|
||||
[rdApplicableSlab]="rdApplicableSlab"
|
||||
[rdMaturityAmount]="rdMaturityAmount"
|
||||
[rdInterestEarned]="rdInterestEarned"
|
||||
[rdDaysInYear]="rdDaysInYear"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-preview-step>
|
||||
|
||||
+102
-28
@@ -75,8 +75,10 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
recurringDepositsAccountTemplate: any;
|
||||
/** Recurring Deposit Account Product Template */
|
||||
recurringDepositsAccountProductTemplate: any;
|
||||
/** Active CBS base interest rate chart */
|
||||
rdInterestRates: any[] = [];
|
||||
/** Selected tenure value and frequency from calculator */
|
||||
selectedTenureValue: number | null = null;
|
||||
selectedTenureFrequencyId: number | null = null;
|
||||
selectedDepositAmount: number | null = null;
|
||||
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { recurringDepositsAccountTemplate: any }) => {
|
||||
@@ -90,14 +92,13 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
*/
|
||||
setTemplate($event: any) {
|
||||
this.recurringDepositsAccountProductTemplate = $event;
|
||||
this.productsService.getRdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => {
|
||||
this.rdInterestRates = rates || [];
|
||||
},
|
||||
error: () => {
|
||||
this.rdInterestRates = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Captures tenure selection from calculator */
|
||||
handleTenureSelected($event: any) {
|
||||
this.selectedTenureValue = $event.depositPeriod;
|
||||
this.selectedTenureFrequencyId = $event.depositPeriodFrequencyId;
|
||||
this.selectedDepositAmount = $event.depositAmount;
|
||||
}
|
||||
|
||||
/** Get Recurring Deposit Account Details Form Data */
|
||||
@@ -123,17 +124,31 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
}
|
||||
|
||||
get rdTenureDays(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.tenureDays ?? null;
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.tenureDays ?? null;
|
||||
}
|
||||
|
||||
get rdApplicableSlab(): any | null {
|
||||
return this.recurringDepositsAccountChartStep?.applicableSlab ?? null;
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.slab ?? null;
|
||||
}
|
||||
|
||||
get rdMaturityAmount(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.maturityAmount ?? null;
|
||||
}
|
||||
|
||||
get rdInterestEarned(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.interestEarned ?? null;
|
||||
}
|
||||
|
||||
get rdDaysInYear(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.daysInYear ?? null;
|
||||
}
|
||||
|
||||
get submittedOnDate(): Date | null {
|
||||
return this.recurringDepositAccountDetailsForm?.value?.submittedOnDate ?? null;
|
||||
}
|
||||
|
||||
get rdCurrencyFlags() {
|
||||
return {
|
||||
balanceSwapOption: this.recurringDepositsAccountCurrencyStep?.balanceSwapOption ?? false,
|
||||
msme: this.recurringDepositsAccountCurrencyStep?.msme ?? false,
|
||||
autoDebitFacility: this.recurringDepositsAccountCurrencyStep?.autoDebitFacility ?? false
|
||||
};
|
||||
}
|
||||
@@ -145,34 +160,93 @@ export class CreateRecurringDepositsAccountComponent {
|
||||
const locale = this.settingsService.language.code;
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
const monthDayFormat = 'dd MMMM';
|
||||
const pt = this.recurringDepositsAccountProductTemplate;
|
||||
|
||||
const details = this.recurringDepositAccount;
|
||||
const currencyStep = this.recurringDepositsAccountCurrencyStep;
|
||||
|
||||
// Terms — pulled from product template
|
||||
const terms: any = {};
|
||||
if (pt?.nominalAnnualInterestRate != null) terms.nominalAnnualInterestRate = pt.nominalAnnualInterestRate;
|
||||
if (pt?.interestCompoundingPeriodType?.id != null)
|
||||
terms.interestCompoundingPeriodType = pt.interestCompoundingPeriodType.id;
|
||||
if (pt?.interestPostingPeriodType?.id != null) terms.interestPostingPeriodType = pt.interestPostingPeriodType.id;
|
||||
if (pt?.interestCalculationType?.id != null) terms.interestCalculationType = pt.interestCalculationType.id;
|
||||
if (pt?.interestCalculationDaysInYearType?.id != null)
|
||||
terms.interestCalculationDaysInYearType = pt.interestCalculationDaysInYearType.id;
|
||||
|
||||
// Settings — pulled from product template
|
||||
const settings: any = {};
|
||||
if (pt?.lockinPeriodFrequency != null) settings.lockinPeriodFrequency = pt.lockinPeriodFrequency;
|
||||
if (pt?.lockinPeriodFrequencyType?.id != null) settings.lockinPeriodFrequencyType = pt.lockinPeriodFrequencyType.id;
|
||||
if (pt?.minDepositTerm != null) settings.minDepositTerm = pt.minDepositTerm;
|
||||
if (pt?.minDepositTermType?.id != null) settings.minDepositTermTypeId = pt.minDepositTermType.id;
|
||||
if (pt?.inMultiplesOfDepositTerm != null) settings.inMultiplesOfDepositTerm = pt.inMultiplesOfDepositTerm;
|
||||
if (pt?.inMultiplesOfDepositTermType?.id != null)
|
||||
settings.inMultiplesOfDepositTermTypeId = pt.inMultiplesOfDepositTermType.id;
|
||||
if (pt?.maxDepositTerm != null) settings.maxDepositTerm = pt.maxDepositTerm;
|
||||
if (pt?.maxDepositTermType?.id != null) settings.maxDepositTermTypeId = pt.maxDepositTermType.id;
|
||||
if (pt?.preClosurePenalApplicable != null) settings.preClosurePenalApplicable = pt.preClosurePenalApplicable;
|
||||
if (pt?.preClosurePenalInterest != null) settings.preClosurePenalInterest = pt.preClosurePenalInterest;
|
||||
if (pt?.preClosurePenalInterestOnType?.id != null)
|
||||
settings.preClosurePenalInterestOnTypeId = pt.preClosurePenalInterestOnType.id;
|
||||
if (pt?.maturityInstructionId != null) settings.maturityInstructionId = pt.maturityInstructionId;
|
||||
if (pt?.transferInterestToSavings != null) settings.transferInterestToSavings = pt.transferInterestToSavings;
|
||||
if (pt?.withHoldTax != null) settings.withHoldTax = pt.withHoldTax;
|
||||
|
||||
// Flags and other product defaults
|
||||
const productDefaults: any = {
|
||||
isCalendarInherited: pt?.isCalendarInherited ?? false,
|
||||
isMandatoryDeposit: pt?.isMandatoryDeposit ?? false,
|
||||
allowWithdrawal: pt?.allowWithdrawal ?? false,
|
||||
adjustAdvanceTowardsFuturePayments: pt?.adjustAdvanceTowardsFuturePayments ?? false,
|
||||
autoDebitFacility: currencyStep?.autoDebitFacility ?? pt?.autoDebitFacility ?? false
|
||||
};
|
||||
|
||||
// Charges from product template
|
||||
const rawCharges: any[] = pt?.charges ?? [];
|
||||
|
||||
const recurringDepositAccount: any = {
|
||||
...details,
|
||||
...terms,
|
||||
...settings,
|
||||
...productDefaults,
|
||||
clientId: this.recurringDepositsAccountTemplate.clientId,
|
||||
charges: [],
|
||||
isCalendarInherited: false,
|
||||
mandatoryRecommendedDepositAmount: 0,
|
||||
recurringFrequency: 1,
|
||||
recurringFrequencyType: 2,
|
||||
mandatoryRecommendedDepositAmount: pt?.minDepositAmount ?? 0,
|
||||
recurringFrequency: pt?.recurringFrequency ?? 1,
|
||||
recurringFrequencyType: pt?.recurringFrequencyType ?? 2, // Monthly
|
||||
submittedOnDate: this.dateUtils.formatDate(details.submittedOnDate, dateFormat),
|
||||
expectedFirstDepositOnDate: this.dateUtils.formatDate(details.expectedFirstDepositOnDate, dateFormat),
|
||||
dateFormat,
|
||||
monthDayFormat,
|
||||
locale,
|
||||
// Currency step overrides
|
||||
balanceSwapOption: currencyStep?.balanceSwapOption ?? details.balanceSwapOption ?? false,
|
||||
msme: currencyStep?.msme ?? details.msme ?? false,
|
||||
autoDebitFacility: currencyStep?.autoDebitFacility ?? details.autoDebitFacility ?? false
|
||||
charts: [
|
||||
{
|
||||
chartSlabs:
|
||||
pt?.accountChart?.chartSlabs ?? pt?.activeChart?.chartSlabs ?? pt?.interestRateCharts?.[0]?.chartSlabs ?? []
|
||||
}
|
||||
],
|
||||
charges: rawCharges.map((charge: any) => ({
|
||||
chargeId: charge.chargeId ?? charge.id,
|
||||
amount: charge.amount,
|
||||
dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat),
|
||||
feeOnMonthDay: charge.feeOnMonthDay,
|
||||
feeInterval: charge.feeInterval
|
||||
}))
|
||||
};
|
||||
|
||||
// Tenure from interest rate chart step (depositPeriod in days)
|
||||
const tenureDays = this.rdTenureDays;
|
||||
if (tenureDays && tenureDays > 0) {
|
||||
recurringDepositAccount.depositPeriod = tenureDays;
|
||||
recurringDepositAccount.depositPeriodFrequencyId = 0; // 0 = Days
|
||||
// Tenure override from interest rate chart calculator
|
||||
if (this.selectedTenureValue && this.selectedTenureFrequencyId !== null) {
|
||||
recurringDepositAccount.depositPeriod = this.selectedTenureValue;
|
||||
recurringDepositAccount.depositPeriodFrequencyId = this.selectedTenureFrequencyId;
|
||||
} else if (this.rdTenureDays) {
|
||||
recurringDepositAccount.depositPeriod = this.rdTenureDays;
|
||||
recurringDepositAccount.depositPeriodFrequencyId = 0; // Days
|
||||
}
|
||||
|
||||
// Amount override from calculator if provided
|
||||
if (this.selectedDepositAmount) {
|
||||
recurringDepositAccount.mandatoryRecommendedDepositAmount = this.selectedDepositAmount;
|
||||
}
|
||||
|
||||
// Nominee from currency step
|
||||
|
||||
+6
-2
@@ -71,7 +71,7 @@
|
||||
<mifosx-recurring-deposits-account-interest-rate-chart-step
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountAndTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[rdInterestRates]="rdInterestRates"
|
||||
[submittedOnDate]="submittedOnDate"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-interest-rate-chart-step>
|
||||
</mat-step>
|
||||
@@ -94,7 +94,11 @@
|
||||
[recurringDepositsAccountTemplate]="recurringDepositsAccountAndTemplate"
|
||||
[recurringDepositsAccountProductTemplate]="recurringDepositsAccountProductTemplate"
|
||||
[recurringDepositAccountData]="recurringDepositAccountData"
|
||||
[rdInterestRates]="rdInterestRates"
|
||||
[rdTenureDays]="rdTenureDays"
|
||||
[rdApplicableSlab]="rdApplicableSlab"
|
||||
[rdMaturityAmount]="rdMaturityAmount"
|
||||
[rdInterestEarned]="rdInterestEarned"
|
||||
[rdDaysInYear]="rdDaysInYear"
|
||||
(submitEvent)="submit()"
|
||||
>
|
||||
</mifosx-recurring-deposits-account-preview-step>
|
||||
|
||||
+36
-10
@@ -67,12 +67,37 @@ export class EditRecurringDepositAccountComponent {
|
||||
@ViewChild(RecurringDepositsAccountChargesStepComponent, { static: true })
|
||||
recurringDepositAccountChargesStep: RecurringDepositsAccountChargesStepComponent;
|
||||
|
||||
@ViewChild(RecurringDepositsAccountInterestRateChartStepComponent)
|
||||
recurringDepositsAccountChartStep: RecurringDepositsAccountInterestRateChartStepComponent;
|
||||
|
||||
get rdTenureDays(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.tenureDays ?? null;
|
||||
}
|
||||
|
||||
get rdApplicableSlab(): any | null {
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.slab ?? null;
|
||||
}
|
||||
|
||||
get rdMaturityAmount(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.maturityAmount ?? null;
|
||||
}
|
||||
|
||||
get rdInterestEarned(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.interestEarned ?? null;
|
||||
}
|
||||
|
||||
get rdDaysInYear(): number | null {
|
||||
return this.recurringDepositsAccountChartStep?.matchResult?.daysInYear ?? null;
|
||||
}
|
||||
|
||||
get submittedOnDate(): Date | null {
|
||||
return this.recurringDepositAccountDetailsForm?.value?.submittedOnDate ?? null;
|
||||
}
|
||||
|
||||
/** Recurring Deposits Account And Template */
|
||||
recurringDepositsAccountAndTemplate: any;
|
||||
/** Recurring Deposit Account Product Template */
|
||||
recurringDepositsAccountProductTemplate: any;
|
||||
/** Active CBS base interest rate chart */
|
||||
rdInterestRates: any[] = [];
|
||||
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { recurringDepositsAccountAndTemplate: any }) => {
|
||||
@@ -86,14 +111,6 @@ export class EditRecurringDepositAccountComponent {
|
||||
*/
|
||||
setTemplate($event: any) {
|
||||
this.recurringDepositsAccountProductTemplate = $event;
|
||||
this.productsService.getRdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => {
|
||||
this.rdInterestRates = rates || [];
|
||||
},
|
||||
error: () => {
|
||||
this.rdInterestRates = [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Get Recurring Deposit Account Details Form Data */
|
||||
@@ -169,6 +186,15 @@ export class EditRecurringDepositAccountComponent {
|
||||
this.recurringDepositAccount.expectedFirstDepositOnDate,
|
||||
dateFormat
|
||||
),
|
||||
charts: [
|
||||
{
|
||||
chartSlabs:
|
||||
this.recurringDepositsAccountProductTemplate?.accountChart?.chartSlabs ??
|
||||
this.recurringDepositsAccountProductTemplate?.activeChart?.chartSlabs ??
|
||||
this.recurringDepositsAccountProductTemplate?.interestRateCharts?.[0]?.chartSlabs ??
|
||||
[]
|
||||
}
|
||||
],
|
||||
dateFormat,
|
||||
monthDayFormat,
|
||||
locale
|
||||
|
||||
-16
@@ -27,22 +27,6 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="racc-alert racc-alert--editable">
|
||||
<div class="racc-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-slide-toggle [(ngModel)]="balanceSwapOption" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="racc-alert racc-alert--editable">
|
||||
<div class="racc-alert__content">
|
||||
<h4>MSME</h4>
|
||||
<p>Micro, Small and Medium Enterprises — mark this account under MSME category.</p>
|
||||
</div>
|
||||
<mat-slide-toggle [(ngModel)]="msme" color="primary"></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div class="racc-alert racc-alert--editable">
|
||||
<div class="racc-alert__content">
|
||||
<h4>Auto Debit Facility</h4>
|
||||
|
||||
-6
@@ -31,15 +31,11 @@ export class RecurringDepositsAccountCurrencyStepComponent implements OnChanges
|
||||
nomineeData: any = null;
|
||||
|
||||
// Editable feature toggles (pre-filled from product, overridable by user)
|
||||
balanceSwapOption = false;
|
||||
msme = false;
|
||||
autoDebitFacility = false;
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['recurringDepositsAccountProductTemplate']?.currentValue) {
|
||||
const pt = changes['recurringDepositsAccountProductTemplate'].currentValue;
|
||||
this.balanceSwapOption = !!pt.balanceSwapOption;
|
||||
this.msme = !!pt.msme;
|
||||
this.autoDebitFacility = !!pt.autoDebitFacility;
|
||||
}
|
||||
}
|
||||
@@ -51,8 +47,6 @@ export class RecurringDepositsAccountCurrencyStepComponent implements OnChanges
|
||||
/** Called by parent to collect currency step values */
|
||||
get currencyStepData() {
|
||||
return {
|
||||
balanceSwapOption: this.balanceSwapOption,
|
||||
msme: this.msme,
|
||||
autoDebitFacility: this.autoDebitFacility,
|
||||
...(this.nomineeData ? { nominee: this.nomineeData } : {})
|
||||
};
|
||||
|
||||
-4
@@ -338,8 +338,6 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
],
|
||||
fieldOfficerId: [''],
|
||||
externalId: [''],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
autoDebitFacility: [false]
|
||||
});
|
||||
}
|
||||
@@ -375,8 +373,6 @@ export class RecurringDepositsAccountDetailsStepComponent implements OnInit {
|
||||
this.loadClientAccounts();
|
||||
}
|
||||
this.recurringDepositAccountDetailsForm.patchValue({
|
||||
balanceSwapOption: !!response.balanceSwapOption,
|
||||
msme: !!response.msme,
|
||||
autoDebitFacility: !!response.autoDebitFacility
|
||||
});
|
||||
this.cdr.detectChanges();
|
||||
|
||||
+349
-37
@@ -6,47 +6,359 @@
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
|
||||
<div class="layout-column gap-2px">
|
||||
<!-- ── Tenure Calculator ───────────────────────────────────────── -->
|
||||
@if (_activeRdVersion) {
|
||||
<div class="tenure-calc">
|
||||
<div class="tenure-calc__header">
|
||||
<i class="fas fa-calculator"></i>
|
||||
<span>Tenure & Interest Calculator</span>
|
||||
</div>
|
||||
<div class="tenure-calc__body">
|
||||
<div class="tenure-calc__input-row">
|
||||
<mat-form-field appearance="outline" class="tenure-calc__field">
|
||||
<mat-label>Enter Tenure (Days)</mat-label>
|
||||
<input matInput type="number" [(ngModel)]="tenureDays" min="1" placeholder="e.g. 365" />
|
||||
<mat-hint>Enter the number of days for your RD</mat-hint>
|
||||
</mat-form-field>
|
||||
<div class="fdrc-wrapper">
|
||||
<!-- ══ Chart Header Card ════════════════════════════════════════════════ -->
|
||||
@if (interestRateChartData.length > 0) {
|
||||
<div class="fdrc-header-card">
|
||||
<div class="fdrc-header-card__left">
|
||||
<div class="fdrc-header-card__icon">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
</div>
|
||||
|
||||
@if (tenureDays && tenureDays > 0) {
|
||||
@if (applicableSlab) {
|
||||
<div class="tenure-calc__result">
|
||||
<div class="tenure-calc__result-row">
|
||||
<span class="tenure-calc__result-label">Tenure</span>
|
||||
<span class="tenure-calc__result-value">{{ tenureDays }} Days</span>
|
||||
</div>
|
||||
<div class="tenure-calc__result-row">
|
||||
<span class="tenure-calc__result-label">Applicable Slab</span>
|
||||
<span class="tenure-calc__result-value">{{ applicableSlab.description }}</span>
|
||||
</div>
|
||||
<div class="tenure-calc__result-row tenure-calc__result-row--highlight">
|
||||
<span class="tenure-calc__result-label">Annual Interest Rate</span>
|
||||
<span class="tenure-calc__result-rate">{{ applicableSlab.annualInterestRate | number: '1.2-2' }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="tenure-calc__no-match">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
No matching slab found for {{ tenureDays }} days
|
||||
</div>
|
||||
<div>
|
||||
<div class="fdrc-header-card__title">{{ chartName || 'RD Interest Rate Chart' }}</div>
|
||||
@if (chartDescription) {
|
||||
<div class="fdrc-header-card__desc">{{ chartDescription }}</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="fdrc-header-card__meta">
|
||||
<div class="fdrc-header-card__dates">
|
||||
<span class="fdrc-meta-item">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
From: <strong>{{ chartFromDate | dateFormat }}</strong>
|
||||
</span>
|
||||
@if (chartEndDate) {
|
||||
<span class="fdrc-meta-item">
|
||||
<i class="fas fa-calendar-times"></i>
|
||||
To: <strong>{{ chartEndDate | dateFormat }}</strong>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@if (isPrimaryGroupingByAmount) {
|
||||
<span class="fdrc-pill fdrc-pill--amount"> <i class="fas fa-layer-group"></i> Grouped by Amount </span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ Tenure Calculator ════════════════════════════════════════════════ -->
|
||||
<div class="fdrc-calc-card">
|
||||
<div class="fdrc-calc-card__header">
|
||||
<div class="fdrc-calc-card__header-left">
|
||||
<div class="fdrc-calc-icon">
|
||||
<i class="fas fa-calculator"></i>
|
||||
</div>
|
||||
<div>
|
||||
<span class="fdrc-calc-card__title">RD Rate Calculator</span>
|
||||
<span class="fdrc-calc-card__sub">Enter your RD tenure to find the applicable interest rate</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdrc-calc-card__body">
|
||||
<div class="fdrc-calc-inputs">
|
||||
<!-- Tenure Value -->
|
||||
<div class="fdrc-calc-field">
|
||||
<label class="fdrc-calc-label">Deposit Tenure</label>
|
||||
<div class="fdrc-tenure-row">
|
||||
<input
|
||||
type="number"
|
||||
class="fdrc-tenure-input"
|
||||
[(ngModel)]="tenureValue"
|
||||
[min]="1"
|
||||
placeholder="Enter value"
|
||||
(keyup.enter)="calculateRate()"
|
||||
/>
|
||||
<!-- Period Unit Selector -->
|
||||
<div class="fdrc-period-tabs">
|
||||
@for (unit of allowedPeriodUnits; track unit) {
|
||||
<button
|
||||
type="button"
|
||||
class="fdrc-period-tab"
|
||||
[class.fdrc-period-tab--active]="tenurePeriodUnit === unit"
|
||||
(click)="tenurePeriodUnit = unit; onPeriodUnitChange()"
|
||||
>
|
||||
{{ unit }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Principal Amount (Monthly Installment) -->
|
||||
<div class="fdrc-calc-field">
|
||||
<label class="fdrc-calc-label">
|
||||
Monthly Installment
|
||||
<span class="fdrc-calc-optional">(optional — for maturity estimation)</span>
|
||||
</label>
|
||||
<div class="fdrc-amount-row">
|
||||
<span class="fdrc-amount-prefix">₹</span>
|
||||
<input
|
||||
type="number"
|
||||
class="fdrc-tenure-input fdrc-tenure-input--amount"
|
||||
[(ngModel)]="principalAmount"
|
||||
[min]="1"
|
||||
placeholder="e.g. 5000"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fdrc-calc-actions">
|
||||
<button type="button" class="fdrc-calc-btn" (click)="calculateRate()">
|
||||
<i class="fas fa-search"></i> Find Rate
|
||||
</button>
|
||||
@if (tenureValue || matchResult) {
|
||||
<button type="button" class="fdrc-calc-reset" (click)="resetCalculator()">
|
||||
<i class="fas fa-times"></i> Reset
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Validation Warning -->
|
||||
@if (validationWarning) {
|
||||
<div class="fdrc-alert fdrc-alert--warn">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<span>{{ validationWarning }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Match Result -->
|
||||
@if (matchResult) {
|
||||
<div class="fdrc-result-card">
|
||||
<div class="fdrc-result-card__check">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="fdrc-result-card__body">
|
||||
<!-- Selected Tenure Banner -->
|
||||
<div class="fdrc-tenure-banner">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
<div class="fdrc-tenure-banner__content">
|
||||
<span class="fdrc-tenure-banner__label">Your Selected RD Tenure</span>
|
||||
<span class="fdrc-tenure-banner__value">{{ tenureValue }} {{ tenurePeriodUnit }}</span>
|
||||
</div>
|
||||
<span class="fdrc-tenure-banner__saved"> <i class="fas fa-save"></i> Will be saved </span>
|
||||
</div>
|
||||
|
||||
<div class="fdrc-result-rate">
|
||||
<span class="fdrc-result-rate__value">{{ matchResult.appliedRate | number: '1.2-2' }}%</span>
|
||||
<span class="fdrc-result-rate__label">Annual Interest Rate</span>
|
||||
</div>
|
||||
<div class="fdrc-result-meta">
|
||||
<div class="fdrc-result-meta__item">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
<span
|
||||
>Applicable Slab:
|
||||
<strong>
|
||||
{{ matchResult.slab.fromPeriod }}–{{ matchResult.slab.toPeriod ?? '∞' }}
|
||||
{{ matchResult.slab.periodType?.value }}
|
||||
</strong>
|
||||
</span>
|
||||
</div>
|
||||
@if (matchResult.slab.description) {
|
||||
<div class="fdrc-result-meta__item">
|
||||
<i class="fas fa-tag"></i>
|
||||
<span>{{ matchResult.slab.description }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Maturity Summary -->
|
||||
@if (matchResult.maturityAmount !== null) {
|
||||
<div class="fdrc-maturity-card">
|
||||
<div class="fdrc-maturity-card__header">
|
||||
<i class="fas fa-coins"></i>
|
||||
<span>RD Maturity Summary (Estimate)</span>
|
||||
</div>
|
||||
<div class="fdrc-maturity-card__body">
|
||||
<div class="fdrc-maturity-row">
|
||||
<span class="fdrc-maturity-row__label">Monthly Installment</span>
|
||||
<span class="fdrc-maturity-row__value">₹ {{ principalAmount | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
<div class="fdrc-maturity-row">
|
||||
<span class="fdrc-maturity-row__label">Tenure</span>
|
||||
<span class="fdrc-maturity-row__value">{{ tenureValue }} {{ tenurePeriodUnit }}</span>
|
||||
</div>
|
||||
<div class="fdrc-maturity-row">
|
||||
<span class="fdrc-maturity-row__label">Interest Rate</span>
|
||||
<span class="fdrc-maturity-row__value">{{ matchResult.appliedRate | number: '1.2-2' }}% p.a.</span>
|
||||
</div>
|
||||
<div class="fdrc-maturity-row fdrc-maturity-row--interest">
|
||||
<span class="fdrc-maturity-row__label">Estimated Interest Earned</span>
|
||||
<span class="fdrc-maturity-row__value">₹ {{ matchResult.interestEarned | number: '1.2-2' }}</span>
|
||||
</div>
|
||||
<div class="fdrc-maturity-row fdrc-maturity-row--total">
|
||||
<span class="fdrc-maturity-row__label">Estimated Maturity Amount</span>
|
||||
<span class="fdrc-maturity-row__value fdrc-maturity-row__value--big">
|
||||
₹ {{ matchResult.maturityAmount | number: '1.2-2' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!matchResult.maturityAmount) {
|
||||
<div class="fdrc-payout-hint">
|
||||
<i class="fas fa-lightbulb"></i>
|
||||
Enter a monthly installment amount above to see the maturity calculation.
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ Rate Chart Table ══════════════════════════════════════════════════ -->
|
||||
<div class="fdrc-table-card">
|
||||
<div class="fdrc-table-card__header">
|
||||
<div class="fdrc-table-card__title-row">
|
||||
<i class="fas fa-table"></i>
|
||||
<span class="fdrc-table-card__title">Interest Rate Slabs</span>
|
||||
@if (interestRateChartData.length) {
|
||||
<span class="fdrc-slab-count"
|
||||
>{{ interestRateChartData.length }} slab{{ interestRateChartData.length !== 1 ? 's' : '' }}</span
|
||||
>
|
||||
}
|
||||
</div>
|
||||
@if (matchResult) {
|
||||
<span class="fdrc-highlight-note"> <i class="fas fa-highlighter"></i> Matched slab is highlighted </span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="fdrc-table-wrap">
|
||||
<table mat-table [dataSource]="interestRateChartData" #chartsTable multiTemplateDataRows class="fdrc-table">
|
||||
<!-- Period Column -->
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Period</th>
|
||||
<td mat-cell *matCellDef="let slab" class="fdrc-td fdrc-td--period">
|
||||
<span class="fdrc-period-range">
|
||||
{{ slab.fromPeriod }}
|
||||
@if (slab.toPeriod !== null) {
|
||||
– {{ slab.toPeriod }}
|
||||
} @else {
|
||||
+
|
||||
}
|
||||
</span>
|
||||
<span class="fdrc-period-type">{{ slab.periodType?.value }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Amount Range Column -->
|
||||
<ng-container matColumnDef="amountRange">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Amount Range</th>
|
||||
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||
@if (slab.amountRangeFrom || slab.amountRangeTo) {
|
||||
<span>{{ slab.amountRangeFrom | number }} – {{ slab.amountRangeTo | number }}</span>
|
||||
} @else {
|
||||
<span class="fdrc-td--na">—</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Interest Column -->
|
||||
<ng-container matColumnDef="interest">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Interest Rate</th>
|
||||
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||
<span class="fdrc-rate-badge">{{ slab.annualInterestRate | number: '1.2-2' }}%</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Description Column -->
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Description</th>
|
||||
<td mat-cell *matCellDef="let slab" class="fdrc-td">
|
||||
{{ slab.description || '—' }}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Actions Column -->
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef class="fdrc-th">Incentives</th>
|
||||
<td mat-cell *matCellDef="let slab; let i = dataIndex" class="fdrc-td">
|
||||
@if (slab.incentives?.length) {
|
||||
<button
|
||||
type="button"
|
||||
class="fdrc-incentive-btn"
|
||||
(click)="expandChartSlabIndex = expandChartSlabIndex === i ? null : i"
|
||||
>
|
||||
<fa-icon [icon]="expandChartSlabIndex !== i ? 'eye' : 'eye-slash'"></fa-icon>
|
||||
{{ expandChartSlabIndex === i ? 'Hide' : 'View' }} ({{ slab.incentives.length }})
|
||||
</button>
|
||||
} @else {
|
||||
<span class="fdrc-td--na">None</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Incentives Expanded Row -->
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let slab; let i = dataIndex"
|
||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
||||
class="fdrc-incentives-cell"
|
||||
>
|
||||
<div
|
||||
class="fdrc-incentives-wrap"
|
||||
[@expandChartSlab]="i === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||
>
|
||||
@if (slab.incentives?.length) {
|
||||
<div class="fdrc-incentives-inner">
|
||||
<div class="fdrc-incentives-title"><i class="fas fa-gift"></i> Incentives for this slab</div>
|
||||
<table mat-table [dataSource]="slab.incentives" class="fdrc-incentives-table">
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>Entity Type</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.entityType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>Attribute</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.attributeName?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>Condition</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.conditionType?.value | titlecase }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>Value</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.attributeValueDesc }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>Type</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.incentiveType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>Amount</th>
|
||||
<td mat-cell *matCellDef="let inc">{{ inc.amount }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns" class="fdrc-header-row"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: chartSlabsDisplayedColumns; let i = dataIndex"
|
||||
class="fdrc-data-row"
|
||||
[class.fdrc-data-row--matched]="isMatchedSlab(i)"
|
||||
></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns"
|
||||
class="fdrc-incentives-row"
|
||||
></tr>
|
||||
</table>
|
||||
|
||||
@if (!interestRateChartData.length) {
|
||||
<div class="fdrc-empty">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
<span>No interest rate slabs defined for this product.</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+789
-78
@@ -6,125 +6,836 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
// ── Tenure Calculator ─────────────────────────────────────────────────────────
|
||||
.tenure-calc {
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
$border: #e2e8f0;
|
||||
$gray-50: #f8f9fa;
|
||||
$gray-100: #f1f5f9;
|
||||
$gray-200: #e8eaed;
|
||||
$gray-600: #64748b;
|
||||
$gray-700: #334155;
|
||||
$gray-900: #0f172a;
|
||||
$blue: #2563eb;
|
||||
$blue-light: #dbeafe;
|
||||
$green: #16a34a;
|
||||
$green-light: #dcfce7;
|
||||
$amber: #d97706;
|
||||
$amber-light: #fef3c7;
|
||||
$purple: #7c3aed;
|
||||
$purple-light: #ede9fe;
|
||||
$red: #dc2626;
|
||||
$red-light: #fee2e2;
|
||||
|
||||
.fdrc-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
// ── Chart Header Card ──────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-header-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 5%);
|
||||
margin-bottom: 1.25rem;
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
}
|
||||
|
||||
&__desc {
|
||||
font-size: 12px;
|
||||
color: $gray-600;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__dates {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
font-size: 12px;
|
||||
color: $gray-700;
|
||||
|
||||
i {
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Pills ──────────────────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
white-space: nowrap;
|
||||
|
||||
&--type {
|
||||
background: #f1f5f9;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
&--single {
|
||||
background: $amber-light;
|
||||
color: $amber;
|
||||
}
|
||||
|
||||
&--double {
|
||||
background: $purple-light;
|
||||
color: $purple;
|
||||
}
|
||||
|
||||
&--special {
|
||||
background: $green-light;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
&--amount {
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Calculator Card ────────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-calc-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: #f0f9ff;
|
||||
border-bottom: 1px solid #bae6fd;
|
||||
padding: 0.65rem 1rem;
|
||||
font-size: 0.85rem;
|
||||
justify-content: space-between;
|
||||
padding: 14px 20px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
}
|
||||
|
||||
&__header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&__sub {
|
||||
font-size: 12px;
|
||||
color: $gray-600;
|
||||
display: block;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-calc-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fdrc-calc-inputs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.fdrc-calc-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
flex: 1 1 220px;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
.fdrc-calc-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $gray-700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.fdrc-calc-optional {
|
||||
font-weight: 400;
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
.fdrc-tenure-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.fdrc-tenure-input {
|
||||
height: 42px;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 8px;
|
||||
padding: 0 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: $gray-900;
|
||||
background: #fff;
|
||||
width: 130px;
|
||||
transition: border-color 0.15s;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $blue;
|
||||
}
|
||||
|
||||
&--amount {
|
||||
width: 180px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-period-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 14px;
|
||||
height: 42px;
|
||||
background: $gray-100;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
.fdrc-period-tabs {
|
||||
display: flex;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fdrc-period-tab {
|
||||
height: 42px;
|
||||
padding: 0 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
background: #fff;
|
||||
color: $gray-600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.15s,
|
||||
color 0.15s;
|
||||
|
||||
& + & {
|
||||
border-left: 1px solid $border;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: $blue;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-amount-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fdrc-amount-prefix {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 42px;
|
||||
background: $gray-100;
|
||||
border: 1.5px solid $border;
|
||||
border-right: none;
|
||||
border-radius: 8px 0 0 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: $gray-700;
|
||||
}
|
||||
|
||||
.fdrc-amount-row .fdrc-tenure-input {
|
||||
border-radius: 0 8px 8px 0;
|
||||
}
|
||||
|
||||
.fdrc-calc-hint {
|
||||
font-size: 11px;
|
||||
color: $amber;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.fdrc-calc-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-shrink: 0;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.fdrc-calc-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
height: 42px;
|
||||
padding: 0 22px;
|
||||
background: $blue;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover {
|
||||
background: #1d4ed8;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-calc-reset {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 42px;
|
||||
padding: 0 16px;
|
||||
background: transparent;
|
||||
color: $gray-600;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: $red;
|
||||
color: $red;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Alert ─────────────────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-alert {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
margin-top: 16px;
|
||||
font-size: 13px;
|
||||
|
||||
i {
|
||||
margin-top: 1px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&--warn {
|
||||
background: $amber-light;
|
||||
color: #b45309;
|
||||
border: 1px solid rgba($amber, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tenure Banner (inside result card) ───────────────────────────────────
|
||||
|
||||
.fdrc-tenure-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 14px;
|
||||
background: #fff;
|
||||
border: 1.5px solid rgba($green, 0.4);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 14px;
|
||||
|
||||
> i {
|
||||
font-size: 18px;
|
||||
color: $green;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
color: #0369a1;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
letter-spacing: 0.06em;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: $gray-900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__saved {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 4px 10px;
|
||||
background: $green-light;
|
||||
color: $green;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Result Card ───────────────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-result-card {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
margin-top: 16px;
|
||||
padding: 18px 20px;
|
||||
background: $green-light;
|
||||
border: 1.5px solid rgba($green, 0.35);
|
||||
border-radius: 12px;
|
||||
|
||||
&__check {
|
||||
font-size: 26px;
|
||||
color: $green;
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-result-rate {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&__value {
|
||||
font-size: 28px;
|
||||
font-weight: 800;
|
||||
color: $green;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 13px;
|
||||
color: #166534;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-result-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
|
||||
&__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font-size: 12px;
|
||||
color: $gray-700;
|
||||
|
||||
i {
|
||||
font-size: 13px;
|
||||
color: $green;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
strong {
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Maturity Card ────────────────────────────────────────────────────────────
|
||||
|
||||
.fdrc-maturity-card {
|
||||
margin-top: 16px;
|
||||
background: #fff;
|
||||
border: 1.5px solid rgba($green, 0.2);
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
background: rgba($green, 0.05);
|
||||
padding: 8px 14px;
|
||||
border-bottom: 1px solid rgba($green, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #166534;
|
||||
|
||||
i {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
&__input-row {
|
||||
padding: 12px 14px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-maturity-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
|
||||
&__label {
|
||||
font-size: 12px;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
&__field {
|
||||
width: 260px;
|
||||
}
|
||||
|
||||
&__result {
|
||||
margin-top: 1rem;
|
||||
border: 1px solid #e0f2fe;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__result-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.55rem 1rem;
|
||||
border-bottom: 1px solid #f0f9ff;
|
||||
font-size: 0.88rem;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
&__value {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
|
||||
&--highlight {
|
||||
background: #f0f9ff;
|
||||
color: $green;
|
||||
}
|
||||
|
||||
&--big {
|
||||
font-size: 16px;
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
|
||||
&__result-label {
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
&--interest {
|
||||
padding-top: 4px;
|
||||
margin-top: 4px;
|
||||
border-top: 1px dashed $border;
|
||||
}
|
||||
|
||||
&__result-value {
|
||||
color: #1e293b;
|
||||
font-weight: 600;
|
||||
&--total {
|
||||
padding-top: 8px;
|
||||
margin-top: 4px;
|
||||
border-top: 1.5px solid $gray-200;
|
||||
}
|
||||
}
|
||||
|
||||
&__result-rate {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
color: #0369a1;
|
||||
.fdrc-payout-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
font-size: 12px;
|
||||
color: $gray-600;
|
||||
font-style: italic;
|
||||
|
||||
i {
|
||||
color: $amber;
|
||||
}
|
||||
}
|
||||
|
||||
&__no-match {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.6rem 1rem;
|
||||
border-radius: 8px;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
// ── Rate Chart Table Card ─────────────────────────────────────────────────
|
||||
|
||||
.fdrc-table-card {
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
justify-content: space-between;
|
||||
padding: 14px 20px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $gray-200;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
i {
|
||||
font-size: 13px;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-t {
|
||||
margin-top: 2em;
|
||||
.fdrc-slab-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 8px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
table {
|
||||
.fdrc-highlight-note {
|
||||
font-size: 12px;
|
||||
color: $green;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.fdrc-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.fdrc-table {
|
||||
width: 100%;
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
tr.incentives-row {
|
||||
height: 0;
|
||||
.fdrc-th {
|
||||
font-size: 11px !important;
|
||||
font-weight: 700 !important;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: $gray-700 !important;
|
||||
background: $gray-50;
|
||||
padding: 10px 16px !important;
|
||||
}
|
||||
|
||||
> td {
|
||||
padding: 0;
|
||||
}
|
||||
.fdrc-td {
|
||||
padding: 12px 16px !important;
|
||||
font-size: 13px;
|
||||
color: $gray-700;
|
||||
vertical-align: middle;
|
||||
|
||||
.incentives {
|
||||
overflow: hidden;
|
||||
}
|
||||
&--period {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.mat-card {
|
||||
border-radius: 0;
|
||||
}
|
||||
&--na {
|
||||
color: #94a3b8;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-period-range {
|
||||
font-weight: 700;
|
||||
color: $gray-900;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.fdrc-period-type {
|
||||
font-size: 11px;
|
||||
color: $gray-600;
|
||||
}
|
||||
|
||||
.fdrc-rate-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 3px 10px;
|
||||
background: $blue-light;
|
||||
color: $blue;
|
||||
border-radius: 999px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fdrc-incentive-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border: 1.5px solid $border;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: $gray-700;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
color 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: $blue;
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-header-row {
|
||||
background: $gray-50;
|
||||
}
|
||||
|
||||
.fdrc-data-row {
|
||||
transition: background 0.15s;
|
||||
|
||||
&:hover td {
|
||||
background: $gray-50;
|
||||
}
|
||||
|
||||
&--matched td {
|
||||
background: $green-light !important;
|
||||
border-left: 3px solid $green;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-incentives-row {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.fdrc-incentives-cell {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.fdrc-incentives-wrap {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.fdrc-incentives-inner {
|
||||
padding: 16px 20px;
|
||||
background: $gray-50;
|
||||
border-bottom: 1px solid $border;
|
||||
}
|
||||
|
||||
.fdrc-incentives-title {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: $gray-700;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
i {
|
||||
color: $amber;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-incentives-table {
|
||||
width: 100%;
|
||||
background: #fff;
|
||||
border: 1px solid $border;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
th {
|
||||
font-size: 10px !important;
|
||||
padding: 8px 12px !important;
|
||||
background: $gray-100;
|
||||
}
|
||||
|
||||
td {
|
||||
font-size: 12px;
|
||||
padding: 8px 12px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.fdrc-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
color: $gray-600;
|
||||
gap: 12px;
|
||||
|
||||
i {
|
||||
font-size: 32px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
+264
-49
@@ -6,8 +6,8 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/** Angular Imports */
|
||||
import { Component, OnInit, Input, OnChanges, ViewChild, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, OnInit, Input, OnChanges, ViewChild, Output, EventEmitter, inject } from '@angular/core';
|
||||
import { RecurringDepositsService } from '../../recurring-deposits.service';
|
||||
import {
|
||||
MatTable,
|
||||
MatColumnDef,
|
||||
@@ -30,6 +30,18 @@ import { YesnoPipe } from '../../../../pipes/yesno.pipe';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
export type RdPeriodUnit = 'Days' | 'Months' | 'Years';
|
||||
|
||||
export interface SlabMatchResult {
|
||||
slab: any;
|
||||
slabIndex: number;
|
||||
appliedRate: number;
|
||||
maturityAmount?: number;
|
||||
interestEarned?: number;
|
||||
tenureDays?: number;
|
||||
daysInYear?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recurring Deposits Account Interest Rate Chart Step
|
||||
*/
|
||||
@@ -64,45 +76,44 @@ import { FormsModule } from '@angular/forms';
|
||||
TitleCasePipe,
|
||||
DateFormatPipe,
|
||||
FormatNumberPipe,
|
||||
YesnoPipe
|
||||
YesnoPipe,
|
||||
DecimalPipe
|
||||
]
|
||||
})
|
||||
export class RecurringDepositsAccountInterestRateChartStepComponent implements OnInit, OnChanges {
|
||||
@Input() recurringDepositsAccountTemplate: any;
|
||||
@Input() recurringDepositsAccountProductTemplate: any;
|
||||
@Input() submittedOnDate: Date | null = null;
|
||||
|
||||
/**
|
||||
* CBS base rate chart loaded from the active base-interest-rate service.
|
||||
* When set, this table is shown instead of the legacy accountChart.chartSlabs.
|
||||
*/
|
||||
@Input() set rdInterestRates(rates: any[]) {
|
||||
this._rdInterestRates = rates || [];
|
||||
this._activeRdVersion = this._rdInterestRates.find((v: any) => v.isActive) ?? this._rdInterestRates[0] ?? null;
|
||||
@Output() tenureSelected = new EventEmitter<{
|
||||
depositPeriod: number;
|
||||
depositPeriodFrequencyId: number;
|
||||
depositAmount?: number;
|
||||
maturityAmount?: number;
|
||||
interestEarned?: number;
|
||||
daysInYear?: number;
|
||||
}>();
|
||||
|
||||
get interestRateChartData(): any[] {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
if (!template) return [];
|
||||
return (
|
||||
template.accountChart?.chartSlabs ??
|
||||
template.activeChart?.chartSlabs ??
|
||||
template.interestRateCharts?.[0]?.chartSlabs ??
|
||||
template.interestRateChart?.chartSlabs ??
|
||||
template.chartSlabs ??
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
_rdInterestRates: any[] = [];
|
||||
_activeRdVersion: any = null;
|
||||
|
||||
readonly rdSlabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
'fromPeriod',
|
||||
'toPeriod',
|
||||
'periodTypeLabel',
|
||||
'annualInterestRate'
|
||||
];
|
||||
|
||||
/** Interest Rate Chart Data (legacy – used when no custom rate chart is available) */
|
||||
interestRateChartData: any = [];
|
||||
/** Columns to be displayed in interest rate chart table. */
|
||||
chartSlabsDisplayedColumns: any[] = [
|
||||
chartSlabsDisplayedColumns: string[] = [
|
||||
'period',
|
||||
'amountRange',
|
||||
'interest',
|
||||
'description',
|
||||
'actions'
|
||||
];
|
||||
/** Columns to be displayed in incentives sub-table. */
|
||||
incentivesDisplayedColumns: string[] = [
|
||||
'entityType',
|
||||
'attributeName',
|
||||
@@ -111,38 +122,242 @@ export class RecurringDepositsAccountInterestRateChartStepComponent implements O
|
||||
'incentiveType',
|
||||
'amount'
|
||||
];
|
||||
/** Additional Column to display incentives table */
|
||||
chartSlabsIncentivesDisplayedColumns: string[] = ['incentives'];
|
||||
/** Expand Chart Slab Index used in the view */
|
||||
expandChartSlabIndex: number;
|
||||
|
||||
/** Tenure entered by user (in days) */
|
||||
tenureDays: number | null = null;
|
||||
expandChartSlabIndex: number | null = null;
|
||||
|
||||
@ViewChild('chartsTable', { static: true }) chartsTableRef: MatTable<Element>;
|
||||
|
||||
constructor() {}
|
||||
// ── Tenure Calculator ───────────────────────────────────────────────────
|
||||
tenureValue: number | null = null;
|
||||
tenurePeriodUnit: RdPeriodUnit = 'Months';
|
||||
principalAmount: number | null = null; // Monthly installment for RD
|
||||
|
||||
/** Find the matching slab for the entered tenure */
|
||||
get applicableSlab(): any | null {
|
||||
if (!this.tenureDays || !this._activeRdVersion?.slabs?.length) return null;
|
||||
const days = this.tenureDays;
|
||||
return (
|
||||
this._activeRdVersion.slabs.find((s: any) => {
|
||||
const from = s.fromPeriod ?? 0;
|
||||
const to = s.toPeriod ?? Infinity;
|
||||
return days >= from && days <= to;
|
||||
}) ?? null
|
||||
);
|
||||
}
|
||||
matchResult: SlabMatchResult | null = null;
|
||||
validationWarning: string | null = null;
|
||||
|
||||
/** Period unit options */
|
||||
readonly allowedPeriodUnits: RdPeriodUnit[] = [
|
||||
'Months',
|
||||
'Years'
|
||||
];
|
||||
|
||||
private recurringDepositsService = inject(RecurringDepositsService);
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.recurringDepositsAccountProductTemplate) {
|
||||
this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
if (template) {
|
||||
if (this.chartsTableRef) {
|
||||
this.chartsTableRef.renderRows();
|
||||
}
|
||||
this.resetCalculator();
|
||||
this.tenurePeriodUnit = this.allowedPeriodUnits[1]; // Default to Months
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.interestRateChartData = [];
|
||||
// Convert entered tenure to Days for slab matching
|
||||
private toDays(value: number, unit: RdPeriodUnit): number {
|
||||
if (unit === 'Days') return value;
|
||||
if (unit === 'Months') return value * 30;
|
||||
return value * 365;
|
||||
}
|
||||
|
||||
// Convert slab period to days
|
||||
private slabPeriodToDays(period: number, periodTypeValue: string): number {
|
||||
const p = (periodTypeValue || '').toLowerCase();
|
||||
if (p.includes('month')) return period * 30;
|
||||
if (p.includes('year')) return period * 365;
|
||||
return period; // days
|
||||
}
|
||||
|
||||
calculateRate() {
|
||||
this.validationWarning = null;
|
||||
this.matchResult = null;
|
||||
|
||||
if (!this.tenureValue || this.tenureValue <= 0) {
|
||||
this.validationWarning = 'Please enter a valid tenure value greater than 0.';
|
||||
return;
|
||||
}
|
||||
|
||||
const enteredDays = this.toDays(this.tenureValue, this.tenurePeriodUnit);
|
||||
const slabs: any[] = this.interestRateChartData || [];
|
||||
|
||||
if (!slabs.length) {
|
||||
this.validationWarning = 'No interest rate slabs are defined for this product.';
|
||||
return;
|
||||
}
|
||||
|
||||
let matchedSlab: any = null;
|
||||
let matchedIndex = -1;
|
||||
|
||||
for (let i = 0; i < slabs.length; i++) {
|
||||
const slab = slabs[i];
|
||||
const periodTypeValue = slab.periodType?.value || 'Days';
|
||||
const fromDays = this.slabPeriodToDays(slab.fromPeriod ?? 0, periodTypeValue);
|
||||
const toDays = slab.toPeriod != null ? this.slabPeriodToDays(slab.toPeriod, periodTypeValue) : Infinity;
|
||||
|
||||
if (enteredDays >= fromDays && enteredDays <= toDays) {
|
||||
matchedSlab = slab;
|
||||
matchedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchedSlab) {
|
||||
this.validationWarning = 'No applicable interest rate slab found for the entered tenure.';
|
||||
return;
|
||||
}
|
||||
|
||||
const rate = matchedSlab.annualInterestRate ?? 0;
|
||||
const result: SlabMatchResult = {
|
||||
slab: matchedSlab,
|
||||
slabIndex: matchedIndex,
|
||||
appliedRate: rate,
|
||||
tenureDays: enteredDays
|
||||
};
|
||||
|
||||
// Maturity Estimation via Backend API
|
||||
if (this.principalAmount && this.principalAmount > 0) {
|
||||
const compoundingType = this.recurringDepositsAccountProductTemplate?.interestCompoundingPeriodType?.id;
|
||||
let compoundingMonths = 1; // Default Monthly
|
||||
if (compoundingType === 5) compoundingMonths = 3;
|
||||
else if (compoundingType === 6) compoundingMonths = 6;
|
||||
else if (compoundingType === 7) compoundingMonths = 12;
|
||||
else if (compoundingType === 1) compoundingMonths = 0; // Special case for Daily in my API logic
|
||||
|
||||
// Calculate exact months based on unit to avoid rounding errors (e.g. 10 years = 120 months, not 122)
|
||||
let tenureInMonths = 0;
|
||||
if (this.tenurePeriodUnit === 'Years') {
|
||||
tenureInMonths = this.tenureValue * 12;
|
||||
} else if (this.tenurePeriodUnit === 'Months') {
|
||||
tenureInMonths = this.tenureValue;
|
||||
} else {
|
||||
tenureInMonths = Math.floor(enteredDays / 30);
|
||||
}
|
||||
|
||||
const calculationData: any = {
|
||||
depositAmount: this.principalAmount,
|
||||
annualInterestRate: rate,
|
||||
tenureInMonths: tenureInMonths,
|
||||
interestCompoundingPeriodInMonths: compoundingMonths
|
||||
};
|
||||
|
||||
if (this.submittedOnDate) {
|
||||
const date = new Date(this.submittedOnDate);
|
||||
calculationData.submittedOnDate = date.toISOString().split('T')[0];
|
||||
calculationData.dateFormat = 'yyyy-MM-dd'; // Standard ISO for API
|
||||
calculationData.locale = 'en';
|
||||
}
|
||||
|
||||
this.recurringDepositsService.calculateRecurringDepositInterest(calculationData).subscribe((apiResult: any) => {
|
||||
result.maturityAmount = apiResult.maturityAmount;
|
||||
result.interestEarned = apiResult.totalInterest;
|
||||
result.daysInYear = apiResult.daysInYear;
|
||||
this.matchResult = { ...result };
|
||||
this.emitTenureSelection();
|
||||
});
|
||||
} else {
|
||||
this.matchResult = result;
|
||||
this.emitTenureSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private emitTenureSelection() {
|
||||
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
|
||||
if (frequencyId !== null && this.matchResult) {
|
||||
this.tenureSelected.emit({
|
||||
depositPeriod: this.tenureValue || 0,
|
||||
depositPeriodFrequencyId: frequencyId,
|
||||
depositAmount: this.principalAmount || undefined,
|
||||
maturityAmount: this.matchResult.maturityAmount,
|
||||
interestEarned: this.matchResult.interestEarned,
|
||||
daysInYear: this.matchResult.daysInYear
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private resolveFrequencyId(unit: RdPeriodUnit): number | null {
|
||||
const options: any[] = this.recurringDepositsAccountProductTemplate?.periodFrequencyTypeOptions ?? [];
|
||||
const unitLower = unit.toLowerCase();
|
||||
const match = options.find((o: any) => (o.value || '').toLowerCase().includes(unitLower));
|
||||
if (match) return match.id;
|
||||
const fallback: Record<RdPeriodUnit, number> = { Days: 0, Months: 2, Years: 3 };
|
||||
return fallback[unit] ?? null;
|
||||
}
|
||||
|
||||
resetCalculator() {
|
||||
this.tenureValue = null;
|
||||
this.principalAmount = null;
|
||||
this.matchResult = null;
|
||||
this.validationWarning = null;
|
||||
this.tenurePeriodUnit = 'Months';
|
||||
}
|
||||
|
||||
onPeriodUnitChange() {
|
||||
this.matchResult = null;
|
||||
this.validationWarning = null;
|
||||
}
|
||||
|
||||
isMatchedSlab(index: number): boolean {
|
||||
return this.matchResult?.slabIndex === index;
|
||||
}
|
||||
|
||||
// ── Header Getters ──────────────────────────────────────────────────────
|
||||
|
||||
get chartName(): string {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return (
|
||||
template?.accountChart?.name ||
|
||||
template?.activeChart?.name ||
|
||||
template?.interestRateCharts?.[0]?.name ||
|
||||
template?.interestRateChart?.name ||
|
||||
template?.name ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
get chartFromDate(): any {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return (
|
||||
template?.accountChart?.fromDate ||
|
||||
template?.activeChart?.fromDate ||
|
||||
template?.interestRateCharts?.[0]?.fromDate ||
|
||||
template?.interestRateChart?.fromDate ||
|
||||
template?.fromDate
|
||||
);
|
||||
}
|
||||
|
||||
get chartEndDate(): any {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return (
|
||||
template?.accountChart?.endDate ||
|
||||
template?.activeChart?.endDate ||
|
||||
template?.interestRateCharts?.[0]?.endDate ||
|
||||
template?.interestRateChart?.endDate ||
|
||||
template?.endDate
|
||||
);
|
||||
}
|
||||
|
||||
get chartDescription(): string {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return (
|
||||
template?.accountChart?.description ||
|
||||
template?.activeChart?.description ||
|
||||
template?.interestRateCharts?.[0]?.description ||
|
||||
template?.interestRateChart?.description ||
|
||||
template?.description ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
get isPrimaryGroupingByAmount(): boolean {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return !!(
|
||||
template?.accountChart?.isPrimaryGroupingByAmount ||
|
||||
template?.activeChart?.isPrimaryGroupingByAmount ||
|
||||
template?.interestRateCharts?.[0]?.isPrimaryGroupingByAmount ||
|
||||
template?.interestRateChart?.isPrimaryGroupingByAmount ||
|
||||
template?.isPrimaryGroupingByAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+210
-180
@@ -422,18 +422,6 @@
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.taxGroup.name }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Balance Swap Option</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.balanceSwapOption">
|
||||
{{ rdCurrencyFlags?.balanceSwapOption | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">MSME</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.msme">
|
||||
{{ rdCurrencyFlags?.msme | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Auto Debit Facility</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="rdCurrencyFlags?.autoDebitFacility">
|
||||
@@ -497,7 +485,7 @@
|
||||
<div class="preview-dorm-step__body">
|
||||
<span class="preview-dorm-step__label">Inactive</span>
|
||||
<span class="preview-dorm-step__days">
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToInactiveFromProduct != null) {
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToInactiveFromProduct !== null) {
|
||||
After {{ recurringDepositsAccountProductTemplate.daysToInactiveFromProduct }} days of no transaction
|
||||
} @else {
|
||||
Not configured
|
||||
@@ -511,7 +499,7 @@
|
||||
<div class="preview-dorm-step__body">
|
||||
<span class="preview-dorm-step__label">Dormant</span>
|
||||
<span class="preview-dorm-step__days">
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToDormancyFromProduct != null) {
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToDormancyFromProduct !== null) {
|
||||
{{ recurringDepositsAccountProductTemplate.daysToDormancyFromProduct }} days after inactive
|
||||
} @else {
|
||||
Not configured
|
||||
@@ -525,7 +513,7 @@
|
||||
<div class="preview-dorm-step__body">
|
||||
<span class="preview-dorm-step__label">Escheat</span>
|
||||
<span class="preview-dorm-step__days">
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToEscheatFromProduct != null) {
|
||||
@if (recurringDepositsAccountProductTemplate?.daysToEscheatFromProduct !== null) {
|
||||
{{ recurringDepositsAccountProductTemplate.daysToEscheatFromProduct }} days after dormant
|
||||
} @else {
|
||||
Not configured
|
||||
@@ -637,183 +625,225 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 7. Interest Rate (Tenure-based result) ════════════════════ -->
|
||||
@if (_activeRdVersion || recurringDepositsAccountProductTemplate?.accountChart) {
|
||||
<!-- ══ 6. Maturity Summary ═══════════════════════════════════ -->
|
||||
@if (rdMaturityAmount) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-piggy-bank preview-section__icon"></i>
|
||||
<span class="preview-section__title">Maturity Summary</span>
|
||||
<span class="preview-section__badge preview-section__badge--active">Projected</span>
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
<div class="preview-grid preview-grid--three">
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Total Deposit Amount</span>
|
||||
<span class="preview-value"
|
||||
>{{ rdMaturityAmount - rdInterestEarned | formatNumber }} {{ currencyName }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Applicable Interest Rate</span>
|
||||
<span class="preview-value">{{ rdApplicableSlab?.annualInterestRate | formatNumber }}%</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Tenure</span>
|
||||
<span class="preview-value">{{ selectedTenureLabel }}</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Estimated Interest Earned</span>
|
||||
<span class="preview-value" style="color: var(--theme-success, #16a34a); font-weight: 500">
|
||||
+ {{ rdInterestEarned | formatNumber }} {{ currencyName }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Days in Year</span>
|
||||
<span class="preview-value">{{ rdDaysInYear ?? 365 }} Days</span>
|
||||
</div>
|
||||
<div class="preview-field" style="grid-column: span 1">
|
||||
<span class="preview-label">Projected Maturity Amount</span>
|
||||
<span class="preview-value preview-mono" style="font-weight: 600; font-size: 1.25rem">
|
||||
{{ rdMaturityAmount | formatNumber }} {{ currencyName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ══ 7. Interest Rate Chart ═══════════════════════════════════ -->
|
||||
@if (interestRateChartData?.length > 0) {
|
||||
<div class="preview-section">
|
||||
<div class="preview-section__header">
|
||||
<i class="fas fa-chart-line preview-section__icon"></i>
|
||||
<span class="preview-section__title">Interest Rate</span>
|
||||
@if (_activeRdVersion) {
|
||||
<span class="preview-section__badge">{{ _activeRdVersion.versionLabel }}</span>
|
||||
<span class="preview-section__title">{{ 'labels.heading.Interest Rate Chart' | translate }}</span>
|
||||
@if (rdTenureDays) {
|
||||
<span class="preview-section__badge preview-section__badge--tenure">
|
||||
<i class="fas fa-calendar-check"></i> Selected Tenure: {{ selectedTenureLabel }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="preview-body">
|
||||
@if (_activeRdVersion) {
|
||||
@if (rdTenureDays && rdApplicableSlab) {
|
||||
<div class="preview-tenure-result">
|
||||
<div class="preview-tenure-result__row">
|
||||
<span class="preview-label">Tenure</span>
|
||||
<span class="preview-value">{{ rdTenureDays }} Days</span>
|
||||
</div>
|
||||
<div class="preview-tenure-result__row">
|
||||
<span class="preview-label">Applicable Slab</span>
|
||||
<span class="preview-value">{{ rdApplicableSlab.description }}</span>
|
||||
</div>
|
||||
<div class="preview-tenure-result__row preview-tenure-result__row--highlight">
|
||||
<span class="preview-label">Annual Interest Rate</span>
|
||||
<span class="preview-tenure-result__rate"
|
||||
>{{ rdApplicableSlab.annualInterestRate | formatNumber }}%</span
|
||||
>
|
||||
<!-- Selected Tenure + Rate Banner -->
|
||||
@if (rdTenureDays && matchedSlabIndex >= 0) {
|
||||
<div class="preview-tenure-banner">
|
||||
<div class="preview-tenure-banner__left">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
<div>
|
||||
<span class="preview-tenure-banner__label">Your RD Tenure</span>
|
||||
<span class="preview-tenure-banner__value">{{ selectedTenureLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<span class="preview-muted">No tenure entered — interest rate not calculated</span>
|
||||
}
|
||||
} @else {
|
||||
<div class="preview-grid preview-grid--three">
|
||||
@if (recurringDepositsAccountProductTemplate?.accountChart?.name) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.name' | translate }}</span>
|
||||
<span class="preview-value">{{ recurringDepositsAccountProductTemplate.accountChart.name }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositsAccountProductTemplate?.accountChart?.fromDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Valid from Date' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate.accountChart.fromDate | dateFormat
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositsAccountProductTemplate?.accountChart?.endDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.End Date' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate.accountChart.endDate | dateFormat
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositsAccountProductTemplate?.accountChart?.description) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Description' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate.accountChart.description
|
||||
}}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.Primary Grouping by Amount' | translate }}</span>
|
||||
<span class="preview-value">{{
|
||||
recurringDepositsAccountProductTemplate?.accountChart?.isPrimaryGroupingByAmount | yesNo
|
||||
}}</span>
|
||||
<div class="preview-tenure-banner__divider"></div>
|
||||
<div class="preview-tenure-banner__right">
|
||||
<span class="preview-tenure-banner__rate-label">Applicable Interest Rate</span>
|
||||
<span class="preview-tenure-banner__rate"
|
||||
>{{ interestRateChartData[matchedSlabIndex]?.annualInterestRate | formatNumber }}%</span
|
||||
>
|
||||
</div>
|
||||
<div class="preview-tenure-banner__slab">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
Slab: {{ interestRateChartData[matchedSlabIndex]?.fromPeriod }}–{{
|
||||
interestRateChartData[matchedSlabIndex]?.toPeriod ?? '∞'
|
||||
}}
|
||||
{{ interestRateChartData[matchedSlabIndex]?.periodType?.value }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (interestRateChartData?.length) {
|
||||
<table
|
||||
class="mat-elevation-z1 irc-table preview-table"
|
||||
mat-table
|
||||
[dataSource]="interestRateChartData"
|
||||
multiTemplateDataRows
|
||||
>
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.fromPeriod }} - {{ chartSlab.toPeriod }} {{
|
||||
chartSlab.periodType?.value | translateKey: 'catalogs'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amountRange">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.amountRangeFrom | formatNumber }} - {{ chartSlab.amountRangeTo | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="interest">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate | formatNumber }} %</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
||||
>
|
||||
<span *ngIf="expandChartSlabIndex !== chartSlabIndex">
|
||||
<fa-icon icon="eye" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.View Incentives' | translate }}
|
||||
</span>
|
||||
<span *ngIf="expandChartSlabIndex === chartSlabIndex">
|
||||
<fa-icon icon="eye-slash" class="m-r-10"></fa-icon>
|
||||
{{ 'labels.buttons.Hide Incentives' | translate }}
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
||||
[attr.colspan]="chartSlabsDisplayedColumns.length"
|
||||
<div class="preview-grid preview-grid--three" style="margin-bottom: 1rem">
|
||||
@if (chartName) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">{{ 'labels.inputs.name' | translate }}</span>
|
||||
<span class="preview-value">{{ chartName }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (chartFromDate) {
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Valid From</span>
|
||||
<span class="preview-value">{{ chartFromDate | dateFormat }}</span>
|
||||
</div>
|
||||
}
|
||||
<div class="preview-field">
|
||||
<span class="preview-label">Primary Grouping by Amount</span>
|
||||
<span class="preview-value preview-badge" [class.preview-badge--yes]="isPrimaryGroupingByAmount">
|
||||
{{ isPrimaryGroupingByAmount | yesNo }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (interestRateChartData?.length) {
|
||||
<table
|
||||
class="mat-elevation-z1 irc-table preview-table"
|
||||
mat-table
|
||||
[dataSource]="interestRateChartData"
|
||||
multiTemplateDataRows
|
||||
>
|
||||
<ng-container matColumnDef="period">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Period' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.fromPeriod }} - {{ chartSlab.toPeriod }} {{
|
||||
chartSlab.periodType?.value | translateKey: 'catalogs'
|
||||
}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amountRange">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Amount Range' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">
|
||||
{{ chartSlab.amountRangeFrom | formatNumber }} - {{ chartSlab.amountRangeTo | formatNumber }}
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="interest">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.annualInterestRate | formatNumber }} %</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="description">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Description' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab">{{ chartSlab.description }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Actions' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let chartSlab; let chartSlabIndex = dataIndex">
|
||||
<button
|
||||
mat-button
|
||||
color="primary"
|
||||
(click)="expandChartSlabIndex = expandChartSlabIndex === chartSlabIndex ? null : chartSlabIndex"
|
||||
>
|
||||
<div
|
||||
class="layout-row-wrap flex-100 incentives"
|
||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||
>
|
||||
<mat-card class="layout-row-wrap flex-100">
|
||||
<h4 class="m-b-10 flex-13">{{ 'labels.heading.Incentives' | translate }}</h4>
|
||||
<table
|
||||
class="flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="chartSlab.incentives"
|
||||
*ngIf="chartSlab.incentives?.length"
|
||||
>
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.entityType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.attributeName?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.conditionType?.value | titlecase }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.attributeValueDesc }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.incentiveType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.amount }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsDisplayedColumns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns"
|
||||
class="incentives-row"
|
||||
></tr>
|
||||
</table>
|
||||
<fa-icon
|
||||
[icon]="expandChartSlabIndex === chartSlabIndex ? 'eye-slash' : 'eye'"
|
||||
class="m-r-10"
|
||||
></fa-icon>
|
||||
{{
|
||||
(expandChartSlabIndex === chartSlabIndex
|
||||
? 'labels.buttons.Hide Incentives'
|
||||
: 'labels.buttons.View Incentives'
|
||||
) | translate
|
||||
}}
|
||||
</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentives">
|
||||
<td
|
||||
mat-cell
|
||||
*matCellDef="let chartSlab; let chartSlabIndex = dataIndex"
|
||||
[attr.colspan]="chartSlabsDisplayedColumns?.length"
|
||||
>
|
||||
<div
|
||||
class="layout-row-wrap flex-100 incentives"
|
||||
[@expandChartSlab]="chartSlabIndex === expandChartSlabIndex ? 'expanded' : 'collapsed'"
|
||||
>
|
||||
<mat-card class="layout-row-wrap flex-100">
|
||||
<h4 class="m-b-10 flex-13">{{ 'labels.heading.Incentives' | translate }}</h4>
|
||||
<table
|
||||
class="flex-100 mat-elevation-z1"
|
||||
mat-table
|
||||
[dataSource]="chartSlab.incentives"
|
||||
*ngIf="chartSlab.incentives?.length"
|
||||
>
|
||||
<ng-container matColumnDef="entityType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Entity Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.entityType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeName">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Name' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.attributeName?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="conditionType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Condition Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.conditionType?.value | titlecase }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="attributeValue">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Attribute Value' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.attributeValueDesc }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="incentiveType">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Incentive Type' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.incentiveType?.value }}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef>{{ 'labels.inputs.Interest' | translate }}</th>
|
||||
<td mat-cell *matCellDef="let incentive">{{ incentive.amount | formatNumber }}</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="incentivesDisplayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: incentivesDisplayedColumns"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="chartSlabsDisplayedColumns"></tr>
|
||||
<tr
|
||||
mat-row
|
||||
*matRowDef="let row; columns: chartSlabsDisplayedColumns; let i = dataIndex"
|
||||
[class.preview-slab-row--matched]="i === matchedSlabIndex"
|
||||
></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: chartSlabsIncentivesDisplayedColumns" class="incentives-row"></tr>
|
||||
</table>
|
||||
@if (rdTenureDays && matchedSlabIndex >= 0) {
|
||||
<div class="preview-slab-legend">
|
||||
<span class="preview-slab-legend__dot"></span>
|
||||
Highlighted row is the slab applicable for your selected tenure of
|
||||
<strong>{{ selectedTenureLabel }}</strong>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
+110
-17
@@ -181,32 +181,125 @@
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
// ── Preview Tenure Result Card ─────────────────────────────────────────────────
|
||||
.preview-tenure-result {
|
||||
border: 1px solid #e0f2fe;
|
||||
// ── Section badge variant for tenure ─────────────────────────────────────────
|
||||
.preview-section__badge--tenure {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
background: #dcfce7;
|
||||
color: #16a34a;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
// ── Tenure + Rate banner ──────────────────────────────────────────────────────
|
||||
.preview-tenure-banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding: 14px 18px;
|
||||
background: #f0fdf4;
|
||||
border: 1.5px solid #86efac;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
&__row {
|
||||
&__left {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.65rem 1rem;
|
||||
border-bottom: 1px solid #f0f9ff;
|
||||
gap: 10px;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&--highlight {
|
||||
background: #f0f9ff;
|
||||
> i {
|
||||
font-size: 20px;
|
||||
color: #16a34a;
|
||||
}
|
||||
}
|
||||
|
||||
&__rate {
|
||||
font-size: 1.5rem;
|
||||
&__label {
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
&__value {
|
||||
display: block;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: #0369a1;
|
||||
color: #0f172a;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
background: #86efac;
|
||||
}
|
||||
|
||||
&__right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__rate-label {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
&__rate {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
color: #16a34a;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
&__slab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
color: #374151;
|
||||
margin-left: auto;
|
||||
|
||||
i {
|
||||
color: #16a34a;
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Matched slab row highlight ────────────────────────────────────────────────
|
||||
.preview-slab-row--matched td {
|
||||
background: #dcfce7 !important;
|
||||
border-left: 3px solid #16a34a;
|
||||
}
|
||||
|
||||
// ── Legend below table ────────────────────────────────────────────────────────
|
||||
.preview-slab-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #4b5563;
|
||||
|
||||
&__dot {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
background: #dcfce7;
|
||||
border: 2px solid #16a34a;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+117
-11
@@ -77,10 +77,10 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
|
||||
@Input() rdCurrencyFlags: any = null;
|
||||
@Input() rdTenureDays: number | null = null;
|
||||
@Input() rdApplicableSlab: any = null;
|
||||
@Input() set rdInterestRates(rates: any[]) {
|
||||
this._rdInterestRates = rates || [];
|
||||
this._activeRdVersion = this._rdInterestRates.find((v: any) => v.isActive) ?? this._rdInterestRates[0] ?? null;
|
||||
}
|
||||
@Input() rdMaturityAmount: number | null = null;
|
||||
@Input() rdInterestEarned: number | null = null;
|
||||
@Input() rdDaysInYear: number | null = null;
|
||||
|
||||
/** Output the submit action */
|
||||
@Output() submitEvent = new EventEmitter();
|
||||
|
||||
@@ -96,9 +96,6 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
|
||||
return this.recurringDepositsAccountProductTemplate?.currency?.inMultiplesOf ?? '—';
|
||||
}
|
||||
|
||||
_rdInterestRates: any[] = [];
|
||||
_activeRdVersion: any = null;
|
||||
|
||||
readonly rdSlabColumns = [
|
||||
'srNo',
|
||||
'description',
|
||||
@@ -117,8 +114,57 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
|
||||
'date',
|
||||
'repaymentsEvery'
|
||||
];
|
||||
/** Interest Rate Chart Data */
|
||||
interestRateChartData: any = [];
|
||||
get interestRateChartData(): any[] {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
if (!template) return [];
|
||||
return (
|
||||
template.accountChart?.chartSlabs ??
|
||||
template.activeChart?.chartSlabs ??
|
||||
template.interestRateCharts?.[0]?.chartSlabs ??
|
||||
template.interestRateChart?.chartSlabs ??
|
||||
template.chartSlabs ??
|
||||
[]
|
||||
);
|
||||
}
|
||||
|
||||
/** Matched Slab Index for highlighting */
|
||||
get matchedSlabIndex(): number {
|
||||
if (!this.rdTenureDays || !this.interestRateChartData) return -1;
|
||||
|
||||
// Find index of the applicable slab in the chart data
|
||||
return this.interestRateChartData.findIndex((slab) => {
|
||||
const pTypeValue = slab.periodType?.value || 'Days';
|
||||
const fromDays = this.slabPeriodToDays(slab.fromPeriod ?? 0, pTypeValue);
|
||||
const toDays = slab.toPeriod != null ? this.slabPeriodToDays(slab.toPeriod, pTypeValue) : Infinity;
|
||||
return this.rdTenureDays >= fromDays && this.rdTenureDays <= toDays;
|
||||
});
|
||||
}
|
||||
|
||||
/** Selected Tenure Label for banner */
|
||||
get selectedTenureLabel(): string {
|
||||
if (!this.rdTenureDays) return '';
|
||||
if (this.rdTenureDays >= 365) {
|
||||
const years = Math.floor(this.rdTenureDays / 365);
|
||||
const rem = this.rdTenureDays % 365;
|
||||
return years + (years === 1 ? ' Year' : ' Years') + (rem > 0 ? ` ${rem} Days` : '');
|
||||
}
|
||||
if (this.rdTenureDays >= 30) {
|
||||
const months = Math.floor(this.rdTenureDays / 30);
|
||||
const rem = this.rdTenureDays % 30;
|
||||
return months + (months === 1 ? ' Month' : ' Months') + (rem > 0 ? ` ${rem} Days` : '');
|
||||
}
|
||||
return this.rdTenureDays + ' Days';
|
||||
}
|
||||
|
||||
private slabPeriodToDays(period: number, periodTypeValue: string): number {
|
||||
const p = (periodTypeValue || '').toLowerCase();
|
||||
if (p.includes('day')) return period;
|
||||
if (p.includes('week')) return period * 7;
|
||||
if (p.includes('month')) return period * 30;
|
||||
if (p.includes('year')) return period * 365;
|
||||
return period;
|
||||
}
|
||||
|
||||
/** Columns to be displayed in interest rate chart table. */
|
||||
chartSlabsDisplayedColumns: any[] = [
|
||||
'period',
|
||||
@@ -144,8 +190,68 @@ export class RecurringDepositsAccountPreviewStepComponent implements OnChanges {
|
||||
constructor() {}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.recurringDepositsAccountProductTemplate) {
|
||||
this.interestRateChartData = this.recurringDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [];
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
if (template) {
|
||||
// Logic for metadata getters is now handled by the dynamic template reference
|
||||
}
|
||||
}
|
||||
|
||||
// ── Header Getters ──────────────────────────────────────────────────────
|
||||
|
||||
get chartName(): string {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return (
|
||||
template?.accountChart?.name ||
|
||||
template?.activeChart?.name ||
|
||||
template?.interestRateCharts?.[0]?.name ||
|
||||
template?.interestRateChart?.name ||
|
||||
template?.name ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
get chartFromDate(): any {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return (
|
||||
template?.accountChart?.fromDate ||
|
||||
template?.activeChart?.fromDate ||
|
||||
template?.interestRateCharts?.[0]?.fromDate ||
|
||||
template?.interestRateChart?.fromDate ||
|
||||
template?.fromDate
|
||||
);
|
||||
}
|
||||
|
||||
get chartEndDate(): any {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return (
|
||||
template?.accountChart?.endDate ||
|
||||
template?.activeChart?.endDate ||
|
||||
template?.interestRateCharts?.[0]?.endDate ||
|
||||
template?.interestRateChart?.endDate ||
|
||||
template?.endDate
|
||||
);
|
||||
}
|
||||
|
||||
get chartDescription(): string {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return (
|
||||
template?.accountChart?.description ||
|
||||
template?.activeChart?.description ||
|
||||
template?.interestRateCharts?.[0]?.description ||
|
||||
template?.interestRateChart?.description ||
|
||||
template?.description ||
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
get isPrimaryGroupingByAmount(): boolean {
|
||||
const template = this.recurringDepositsAccountProductTemplate || this.recurringDepositsAccountTemplate;
|
||||
return !!(
|
||||
template?.accountChart?.isPrimaryGroupingByAmount ||
|
||||
template?.activeChart?.isPrimaryGroupingByAmount ||
|
||||
template?.interestRateCharts?.[0]?.isPrimaryGroupingByAmount ||
|
||||
template?.interestRateChart?.isPrimaryGroupingByAmount ||
|
||||
template?.isPrimaryGroupingByAmount
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@
|
||||
<span>Interest Rate & Posting Terms</span>
|
||||
</div>
|
||||
<div class="rdpt-body">
|
||||
@if (recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate != null) {
|
||||
@if (recurringDepositsAccountProductTemplate?.nominalAnnualInterestRate !== null) {
|
||||
<div class="rdpt-banner">
|
||||
<span class="rdpt-banner__icon">ℹ️</span>
|
||||
<span>
|
||||
|
||||
+6
-1
@@ -92,7 +92,12 @@ export class InterestRateChartTabComponent {
|
||||
*/
|
||||
constructor() {
|
||||
this.route.parent.data.subscribe((data: { recurringDepositsAccountData: any }) => {
|
||||
this.interestRateChartData = data.recurringDepositsAccountData.accountChart.chartSlabs;
|
||||
const accountData = data.recurringDepositsAccountData;
|
||||
this.interestRateChartData =
|
||||
accountData.accountChart?.chartSlabs ??
|
||||
accountData.activeChart?.chartSlabs ??
|
||||
accountData.interestRateCharts?.[0]?.chartSlabs ??
|
||||
[];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,4 +173,11 @@ export class RecurringDepositsService {
|
||||
params: httpParams
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Calculates the interest for a recurring deposit account
|
||||
* @param data Calculation data
|
||||
*/
|
||||
calculateRecurringDepositInterest(data: any): Observable<any> {
|
||||
return this.http.post(`/recurringdepositaccounts/calculate-rd-interest`, data);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,6 @@ import { EditTaxComponentComponent } from './manage-tax-components/edit-tax-comp
|
||||
import { ViewTaxGroupComponent } from './manage-tax-groups/view-tax-group/view-tax-group.component';
|
||||
import { ShareProductsDividendsComponent } from './share-products/dividends-share-product/dividends.components';
|
||||
import { CreateRecurringDepositProductComponent } from './recurring-deposit-products/create-recurring-deposit-product/create-recurring-deposit-product.component';
|
||||
import { BaseRecurringDepositInterestRateChartComponent } from './recurring-deposit-products/base-recurring-deposit-interest-rate-chart/base-recurring-deposit-interest-rate-chart.component';
|
||||
import { CreateDividendComponent } from './share-products/create-dividend/create-dividend.component';
|
||||
import { ViewFixedDepositProductComponent } from './fixed-deposit-products/view-fixed-deposit-product/view-fixed-deposit-product.component';
|
||||
import { ViewDividendComponent } from './share-products/view-dividend/view-dividend.component';
|
||||
@@ -655,14 +654,6 @@ const routes: Routes = [
|
||||
path: 'recurring-deposit-products',
|
||||
data: { title: 'Recurring Deposit Products', breadcrumb: 'Recurring Deposit Products' },
|
||||
children: [
|
||||
{
|
||||
path: 'base-interest-rates',
|
||||
component: BaseRecurringDepositInterestRateChartComponent,
|
||||
data: { title: 'CBS Base Interest Rates', breadcrumb: 'Base Interest Rates' },
|
||||
resolve: {
|
||||
recurringDepositProductsTemplate: RecurringDepositProductsTemplateResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'create',
|
||||
component: CreateRecurringDepositProductComponent,
|
||||
@@ -717,14 +708,6 @@ const routes: Routes = [
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'interest-rates',
|
||||
component: RdInterestRatesTabComponent,
|
||||
data: { title: 'CBS Base Chart', breadcrumb: 'CBS Base Chart', routeParamBreadcrumb: false },
|
||||
resolve: {
|
||||
rdInterestRates: rdInterestRatesResolver
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+100
-7
@@ -34,6 +34,7 @@ import {
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
|
||||
import { RecurringDepositProductPreviewStepComponent } from '../recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
@@ -72,6 +73,7 @@ export class CreateRecurringDepositProductComponent {
|
||||
private router = inject(Router);
|
||||
private settingsService = inject(SettingsService);
|
||||
private accounting = inject(Accounting);
|
||||
private dateUtils = inject(Dates);
|
||||
|
||||
@ViewChild(RecurringDepositProductDetailsStepComponent, { static: true })
|
||||
recurringDepositProductDetailsStep: RecurringDepositProductDetailsStepComponent;
|
||||
@@ -112,7 +114,7 @@ export class CreateRecurringDepositProductComponent {
|
||||
});
|
||||
this.accountingRuleData = this.accounting.getAccountingRulesForSavings();
|
||||
// Load the CBS base chart as the reference for the editable table
|
||||
this.productsService.getRdBaseInterestRates().subscribe({
|
||||
this.productsService.getFdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => {
|
||||
this.rdInterestRates = rates || [];
|
||||
},
|
||||
@@ -151,7 +153,18 @@ export class CreateRecurringDepositProductComponent {
|
||||
}
|
||||
|
||||
get activeBaseVersion() {
|
||||
return this.rdInterestRates.find((version: any) => version.isActive) ?? this.rdInterestRates[0] ?? null;
|
||||
const chart =
|
||||
this.rdInterestRates.find((version: any) => version.fdType === 'DOUBLE' && version.isActive) ??
|
||||
this.rdInterestRates.find((version: any) => version.fdType === 'DOUBLE') ??
|
||||
this.rdInterestRates[0];
|
||||
if (chart) {
|
||||
return {
|
||||
...chart,
|
||||
versionLabel: chart.versionLabel?.replace('FD', 'RD'),
|
||||
remarks: chart.remarks?.replace('FD', 'RD')
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get activeBaseSlabs() {
|
||||
@@ -166,17 +179,66 @@ export class CreateRecurringDepositProductComponent {
|
||||
...this.recurringDepositProductSettingsStep.recurringDepositProductSettings,
|
||||
...this.recurringDepositProductChargesStep.recurringDepositProductCharges,
|
||||
...this.recurringDepositProductAccountingStep.recurringDepositProductAccounting,
|
||||
charts: this.recurringDepositProductCharts,
|
||||
rdSlabs: this.activeBaseSlabs
|
||||
};
|
||||
}
|
||||
|
||||
get recurringDepositProductCharts() {
|
||||
const activeBaseVersion = this.activeBaseVersion;
|
||||
if (!activeBaseVersion?.slabs?.length) {
|
||||
return [];
|
||||
}
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
return [
|
||||
{
|
||||
name: activeBaseVersion.versionLabel || 'CBS Base Chart',
|
||||
description: activeBaseVersion.remarks || activeBaseVersion.versionLabel || 'CBS base chart',
|
||||
fromDate:
|
||||
this.dateUtils.formatDate(activeBaseVersion.effectiveFromDate, dateFormat) ||
|
||||
activeBaseVersion.effectiveFromDate,
|
||||
...(activeBaseVersion.effectiveToDate
|
||||
? {
|
||||
endDate:
|
||||
this.dateUtils.formatDate(activeBaseVersion.effectiveToDate, dateFormat) ||
|
||||
activeBaseVersion.effectiveToDate
|
||||
}
|
||||
: {}),
|
||||
isPrimaryGroupingByAmount: false,
|
||||
locale: this.settingsService.language.code,
|
||||
dateFormat,
|
||||
chartSlabs: activeBaseVersion.slabs.map((slab: any) => {
|
||||
const chartSlab: any = {
|
||||
periodType: slab.periodTypeEnum,
|
||||
fromPeriod: slab.fromPeriod,
|
||||
toPeriod: slab.toPeriod,
|
||||
annualInterestRate: slab.annualInterestRate,
|
||||
description: slab.description || '',
|
||||
incentives: [] as any[]
|
||||
};
|
||||
|
||||
if (slab.amountRangeFrom !== null && slab.amountRangeFrom !== undefined && slab.amountRangeFrom !== '') {
|
||||
chartSlab.amountRangeFrom = slab.amountRangeFrom;
|
||||
}
|
||||
|
||||
if (slab.amountRangeTo !== null && slab.amountRangeTo !== undefined && slab.amountRangeTo !== '') {
|
||||
chartSlab.amountRangeTo = slab.amountRangeTo;
|
||||
}
|
||||
|
||||
return chartSlab;
|
||||
})
|
||||
}
|
||||
] as any[];
|
||||
}
|
||||
|
||||
submit() {
|
||||
// TODO: Update once language and date settings are setup
|
||||
const recurringDepositProduct = {
|
||||
const recurringDepositProduct: any = this.pruneEmptyValues({
|
||||
...this.recurringDepositProduct,
|
||||
charges: this.recurringDepositProduct.charges.map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code // locale required for depositAmount
|
||||
};
|
||||
charts: this.recurringDepositProductCharts,
|
||||
charges: (this.recurringDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code
|
||||
});
|
||||
if (!recurringDepositProduct.description) {
|
||||
recurringDepositProduct.description = '';
|
||||
}
|
||||
@@ -191,4 +253,35 @@ export class CreateRecurringDepositProductComponent {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private pruneEmptyValues<T>(value: T): T {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) => this.pruneEmptyValues(item)).filter((item) => item !== undefined) as T;
|
||||
}
|
||||
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.entries(value as Record<string, any>).reduce(
|
||||
(acc, [
|
||||
key,
|
||||
currentValue
|
||||
]) => {
|
||||
const cleanedValue = this.pruneEmptyValues(currentValue);
|
||||
if (cleanedValue === undefined) {
|
||||
return acc;
|
||||
}
|
||||
return {
|
||||
...acc,
|
||||
[key]: cleanedValue
|
||||
};
|
||||
},
|
||||
{} as Record<string, any>
|
||||
) as T;
|
||||
}
|
||||
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
+66
-2
@@ -34,6 +34,7 @@ import {
|
||||
import { ProductsService } from 'app/products/products.service';
|
||||
import { SettingsService } from 'app/settings/settings.service';
|
||||
import { Accounting } from 'app/core/utils/accounting';
|
||||
import { Dates } from 'app/core/utils/dates';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { RecurringDepositProductPreviewStepComponent } from '../recurring-deposit-product-stepper/recurring-deposit-product-preview-step/recurring-deposit-product-preview-step.component';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
@@ -70,6 +71,7 @@ export class EditRecurringDepositProductComponent {
|
||||
private router = inject(Router);
|
||||
private settingsService = inject(SettingsService);
|
||||
private accounting = inject(Accounting);
|
||||
private dateUtils = inject(Dates);
|
||||
|
||||
@ViewChild(RecurringDepositProductDetailsStepComponent, { static: true })
|
||||
recurringDepositProductDetailsStep: RecurringDepositProductDetailsStepComponent;
|
||||
@@ -107,7 +109,7 @@ export class EditRecurringDepositProductComponent {
|
||||
constructor() {
|
||||
this.route.data.subscribe((data: { recurringDepositProductAndTemplate: any }) => {
|
||||
this.recurringDepositProductsTemplate = data.recurringDepositProductAndTemplate;
|
||||
this.productsService.getRdBaseInterestRates().subscribe({
|
||||
this.productsService.getFdBaseInterestRates().subscribe({
|
||||
next: (rates: any[]) => {
|
||||
this.rdInterestRates = rates || [];
|
||||
},
|
||||
@@ -148,7 +150,18 @@ export class EditRecurringDepositProductComponent {
|
||||
}
|
||||
|
||||
get activeBaseVersion() {
|
||||
return this.rdInterestRates.find((version: any) => version.isActive) ?? this.rdInterestRates[0] ?? null;
|
||||
const chart =
|
||||
this.rdInterestRates.find((version: any) => version.fdType === 'DOUBLE' && version.isActive) ??
|
||||
this.rdInterestRates.find((version: any) => version.fdType === 'DOUBLE') ??
|
||||
this.rdInterestRates[0];
|
||||
if (chart) {
|
||||
return {
|
||||
...chart,
|
||||
versionLabel: chart.versionLabel?.replace('FD', 'RD'),
|
||||
remarks: chart.remarks?.replace('FD', 'RD')
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
get activeBaseSlabs() {
|
||||
@@ -163,6 +176,7 @@ export class EditRecurringDepositProductComponent {
|
||||
...this.recurringDepositProductSettingsStep.recurringDepositProductSettings,
|
||||
...this.recurringDepositProductChargesStep.recurringDepositProductCharges,
|
||||
...this.recurringDepositProductAccountingStep.recurringDepositProductAccounting,
|
||||
charts: this.recurringDepositProductCharts,
|
||||
rdSlabs: this.activeBaseSlabs
|
||||
};
|
||||
}
|
||||
@@ -170,13 +184,63 @@ export class EditRecurringDepositProductComponent {
|
||||
get recurringDepositProductPreview() {
|
||||
return {
|
||||
...this.recurringDepositProduct,
|
||||
charts: this.recurringDepositProductCharts,
|
||||
rdSlabs: this.activeBaseSlabs
|
||||
};
|
||||
}
|
||||
|
||||
get recurringDepositProductCharts() {
|
||||
const activeBaseVersion = this.activeBaseVersion;
|
||||
if (!activeBaseVersion?.slabs?.length) {
|
||||
return [];
|
||||
}
|
||||
const dateFormat = this.settingsService.dateFormat;
|
||||
|
||||
return [
|
||||
{
|
||||
name: activeBaseVersion.versionLabel || 'CBS Base Chart',
|
||||
description: activeBaseVersion.remarks || activeBaseVersion.versionLabel || 'CBS base chart',
|
||||
fromDate:
|
||||
this.dateUtils.formatDate(activeBaseVersion.effectiveFromDate, dateFormat) ||
|
||||
activeBaseVersion.effectiveFromDate,
|
||||
...(activeBaseVersion.effectiveToDate
|
||||
? {
|
||||
endDate:
|
||||
this.dateUtils.formatDate(activeBaseVersion.effectiveToDate, dateFormat) ||
|
||||
activeBaseVersion.effectiveToDate
|
||||
}
|
||||
: {}),
|
||||
isPrimaryGroupingByAmount: false,
|
||||
locale: this.settingsService.language.code,
|
||||
dateFormat,
|
||||
chartSlabs: activeBaseVersion.slabs.map((slab: any) => {
|
||||
const chartSlab: any = {
|
||||
periodType: slab.periodTypeEnum,
|
||||
fromPeriod: slab.fromPeriod,
|
||||
toPeriod: slab.toPeriod,
|
||||
annualInterestRate: slab.annualInterestRate,
|
||||
description: slab.description || '',
|
||||
incentives: [] as any[]
|
||||
};
|
||||
|
||||
if (slab.amountRangeFrom !== null && slab.amountRangeFrom !== undefined && slab.amountRangeFrom !== '') {
|
||||
chartSlab.amountRangeFrom = slab.amountRangeFrom;
|
||||
}
|
||||
|
||||
if (slab.amountRangeTo !== null && slab.amountRangeTo !== undefined && slab.amountRangeTo !== '') {
|
||||
chartSlab.amountRangeTo = slab.amountRangeTo;
|
||||
}
|
||||
|
||||
return chartSlab;
|
||||
})
|
||||
}
|
||||
] as any[];
|
||||
}
|
||||
|
||||
submit() {
|
||||
const recurringDepositProduct: any = this.pruneEmptyValues({
|
||||
...this.recurringDepositProduct,
|
||||
charts: this.recurringDepositProductCharts,
|
||||
charges: (this.recurringDepositProduct.charges ?? []).map((charge: any) => ({ id: charge.id })),
|
||||
locale: this.settingsService.language.code
|
||||
});
|
||||
|
||||
-22
@@ -63,28 +63,6 @@
|
||||
</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>
|
||||
|
||||
-6
@@ -55,8 +55,6 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
daysToInactive: this.recurringDepositProductsTemplate.daysToInactive ?? 30,
|
||||
daysToDormancy: this.recurringDepositProductsTemplate.daysToDormancy ?? 60,
|
||||
daysToEscheat: this.recurringDepositProductsTemplate.daysToEscheat ?? 90,
|
||||
balanceSwapOption: this.recurringDepositProductsTemplate.balanceSwapOption ?? false,
|
||||
msme: this.recurringDepositProductsTemplate.msme ?? false,
|
||||
nominationFacility: this.recurringDepositProductsTemplate.nominationFacility ?? false,
|
||||
autoDebitFacility: this.recurringDepositProductsTemplate.autoDebitFacility ?? false
|
||||
});
|
||||
@@ -80,8 +78,6 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
daysToInactive: [30],
|
||||
daysToDormancy: [60],
|
||||
daysToEscheat: [90],
|
||||
balanceSwapOption: [false],
|
||||
msme: [false],
|
||||
nominationFacility: [false],
|
||||
autoDebitFacility: [false]
|
||||
});
|
||||
@@ -93,8 +89,6 @@ export class RecurringDepositProductCurrencyStepComponent implements OnInit {
|
||||
currencyCode: formValue.currencyCode,
|
||||
digitsAfterDecimal: formValue.digitsAfterDecimal,
|
||||
dormancyAlert: formValue.dormancyAlert,
|
||||
balanceSwapOption: formValue.balanceSwapOption,
|
||||
msme: formValue.msme,
|
||||
nominationFacility: formValue.nominationFacility,
|
||||
autoDebitFacility: formValue.autoDebitFacility
|
||||
};
|
||||
|
||||
-9
@@ -78,15 +78,6 @@
|
||||
<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>GL Account (Liability)</mat-label>
|
||||
<mat-select formControlName="glAccountId" [compareWith]="compareById">
|
||||
|
||||
-3
@@ -72,7 +72,6 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh
|
||||
null,
|
||||
Validators.required
|
||||
],
|
||||
customerSegment: [null],
|
||||
glAccountId: [null],
|
||||
effectiveTo: [''],
|
||||
endDate: [''],
|
||||
@@ -107,7 +106,6 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh
|
||||
|
||||
private applyTemplate(template: any) {
|
||||
this.accountTypeData = this.getOptionNames(template.accountTypeOptions);
|
||||
this.customerSegmentData = this.getOptionNames(template.customerSegmentOptions);
|
||||
this.liabilityAccountOptions = template.accountingMappingOptions?.liabilityAccountOptions ?? [];
|
||||
this.cdr.detectChanges();
|
||||
|
||||
@@ -117,7 +115,6 @@ export class RecurringDepositProductDetailsStepComponent implements OnInit, OnCh
|
||||
shortName: isEditing ? template.shortName : template.shortName || '',
|
||||
description: template.description || '',
|
||||
accountType: template.accountType ?? this.accountTypeData[0] ?? null,
|
||||
customerSegment: template.customerSegment ?? this.customerSegmentData[0] ?? null,
|
||||
glAccountId: template.glAccountId ?? null,
|
||||
effectiveTo: this.parseDate(template.effectiveTo),
|
||||
endDate: this.parseDate(template.endDate),
|
||||
|
||||
+25
-23
@@ -1,4 +1,4 @@
|
||||
<!--
|
||||
<!--
|
||||
Copyright since 2025 Mifos Initiative
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
@@ -46,13 +46,6 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositProduct.customerSegment) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Customer Segment:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.customerSegment }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositProduct.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
@@ -116,20 +109,6 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositProduct.balanceSwapOption !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Balance Swap Option:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.balanceSwapOption | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositProduct.msme !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">MSME:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.msme | yesNo }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositProduct.nominationFacility !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Nomination Facility:</span>
|
||||
@@ -144,6 +123,28 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (
|
||||
recurringDepositProduct.minDepositAmount !== undefined || recurringDepositProduct.maxDepositAmount !== undefined
|
||||
) {
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">Deposit Amount</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
@if (recurringDepositProduct.minDepositAmount !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Minimum:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.minDepositAmount }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (recurringDepositProduct.maxDepositAmount !== undefined) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Maximum:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.maxDepositAmount }}</span>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- ── Terms ── -->
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.inputs.Terms' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
@@ -324,8 +325,9 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- ── Interest Rate Slabs ── -->
|
||||
<!-- ── Interest Rate Slabs ── -->
|
||||
@if (recurringDepositProduct.rdSlabs?.length) {
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
<h3 class="mat-h3 flex-100">{{ 'labels.heading.Interest Rate Charts' | translate }}</h3>
|
||||
<mat-divider class="flex-100"></mat-divider>
|
||||
|
||||
|
||||
+39
@@ -7,6 +7,25 @@
|
||||
-->
|
||||
|
||||
<form [formGroup]="recurringDepositProductTermsForm">
|
||||
<div class="rdpt-section">
|
||||
<div class="rdpt-section__header">
|
||||
<span class="rdpt-section__badge"><i class="fas fa-coins"></i></span>
|
||||
<span>Deposit Amount</span>
|
||||
</div>
|
||||
<div class="rdpt-body">
|
||||
<div class="rdpt-grid rdpt-grid--three">
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>Minimum</mat-label>
|
||||
<input type="number" matInput formControlName="minDepositAmount" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>Maximum</mat-label>
|
||||
<input type="number" matInput formControlName="maxDepositAmount" min="0" step="0.01" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rdpt-section">
|
||||
<div class="rdpt-section__header">
|
||||
<span class="rdpt-section__badge"><i class="fas fa-chart-line"></i></span>
|
||||
@@ -78,6 +97,26 @@
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>Interest Compounding Period</mat-label>
|
||||
<mat-select formControlName="interestCompoundingPeriodType" required>
|
||||
@for (opt of interestCompoundingPeriodTypeData; track opt.id) {
|
||||
<mat-option [value]="opt.id">{{ opt.value | translateKey: 'catalogs' }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Interest Compounding Period <strong>is required</strong></mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>Days in Year</mat-label>
|
||||
<mat-select formControlName="interestCalculationDaysInYearType" required>
|
||||
@for (opt of interestCalculationDaysInYearTypeData; track opt.id) {
|
||||
<mat-option [value]="opt.id">{{ opt.value | translateKey: 'catalogs' }}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
<mat-error>Days in Year <strong>is required</strong></mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
@if (recurringDepositProductTermsForm.value.interestCalculationType === 2) {
|
||||
<mat-form-field appearance="outline" class="rdpt-field">
|
||||
<mat-label>Average Period</mat-label>
|
||||
|
||||
+8
-10
@@ -85,7 +85,9 @@ export class RecurringDepositProductTermsStepComponent implements OnInit {
|
||||
frequencyFixDay: isEditing ? (this.recurringDepositProductsTemplate.frequencyFixDay ?? 30) : 30,
|
||||
lastInterestDate: this.recurringDepositProductsTemplate.lastInterestDate
|
||||
? this.parseDate(this.recurringDepositProductsTemplate.lastInterestDate)
|
||||
: ''
|
||||
: '',
|
||||
minDepositAmount: this.recurringDepositProductsTemplate.minDepositAmount ?? '',
|
||||
maxDepositAmount: this.recurringDepositProductsTemplate.maxDepositAmount ?? ''
|
||||
});
|
||||
}
|
||||
|
||||
@@ -145,7 +147,9 @@ export class RecurringDepositProductTermsStepComponent implements OnInit {
|
||||
30,
|
||||
[Validators.min(0)]
|
||||
],
|
||||
lastInterestDate: ['']
|
||||
lastInterestDate: [''],
|
||||
minDepositAmount: [''],
|
||||
maxDepositAmount: ['']
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,14 +160,8 @@ export class RecurringDepositProductTermsStepComponent implements OnInit {
|
||||
if (formValue.effectiveDate instanceof Date) {
|
||||
formValue.effectiveDate = this.dateUtils.formatDate(formValue.effectiveDate, dateFormat) || '';
|
||||
}
|
||||
if (formValue.lastInterestDate instanceof Date) {
|
||||
formValue.lastInterestDate = this.dateUtils.formatDate(formValue.lastInterestDate, dateFormat) || '';
|
||||
}
|
||||
for (const key in formValue) {
|
||||
if (formValue[key] === '') {
|
||||
delete formValue[key];
|
||||
}
|
||||
}
|
||||
formValue.minDepositAmount = formValue.minDepositAmount;
|
||||
formValue.maxDepositAmount = formValue.maxDepositAmount;
|
||||
return formValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,18 +20,6 @@
|
||||
{{ 'labels.buttons.Create Recurring Deposit Product' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="in-block">
|
||||
<button
|
||||
mat-stroked-button
|
||||
color="primary"
|
||||
[routerLink]="['/products/recurring-deposit-products/base-interest-rates']"
|
||||
matTooltip="Open the CBS base interest chart"
|
||||
matTooltipPosition="above"
|
||||
>
|
||||
<fa-icon icon="chart-line" class="m-r-10"></fa-icon>
|
||||
CBS Base Chart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
-14
@@ -48,12 +48,6 @@
|
||||
<span class="flex-60">{{ recurringDepositProduct.accountType }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositProduct.customerSegment) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Customer Segment:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.customerSegment }}</span>
|
||||
</div>
|
||||
}
|
||||
@if (recurringDepositProduct.glAccountId) {
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">GL Account (Liability):</span>
|
||||
@@ -99,14 +93,6 @@
|
||||
<span class="flex-40">Dormancy Alert:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.dormancyAlert | yesNo }}</span>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Balance Swap Option:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.balanceSwapOption | yesNo }}</span>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">MSME:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.msme | yesNo }}</span>
|
||||
</div>
|
||||
<div class="flex-100 layout-row">
|
||||
<span class="flex-40">Nomination Facility:</span>
|
||||
<span class="flex-60">{{ recurringDepositProduct.nominationFacility | yesNo }}</span>
|
||||
|
||||
-9
@@ -18,15 +18,6 @@
|
||||
>
|
||||
{{ 'labels.heading.General' | translate }}
|
||||
</a>
|
||||
<a
|
||||
mat-tab-link
|
||||
[routerLink]="['./interest-rates']"
|
||||
routerLinkActive
|
||||
#interestRates="routerLinkActive"
|
||||
[active]="interestRates.isActive"
|
||||
>
|
||||
CBS Base Chart
|
||||
</a>
|
||||
@for (recurringDepositDatatable of recurringDepositDatatables; track recurringDepositDatatable) {
|
||||
<a
|
||||
mat-tab-link
|
||||
|
||||
@@ -19,7 +19,6 @@ import { VersionService } from 'app/system/version.service';
|
||||
/** Environment Configuration */
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { NgClass, DatePipe } from '@angular/common';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
/**
|
||||
@@ -30,9 +29,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
templateUrl: './footer.component.html',
|
||||
styleUrls: ['./footer.component.scss'],
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
NgClass,
|
||||
DatePipe
|
||||
...STANDALONE_SHARED_IMPORTS
|
||||
]
|
||||
})
|
||||
export class FooterComponent implements OnInit, OnDestroy {
|
||||
|
||||
@@ -26,7 +26,7 @@ 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 { MatCard, MatCardTitle, MatCardContent } from '@angular/material/card';
|
||||
import { MatCard, MatCardContent } from '@angular/material/card';
|
||||
import { MatCheckbox } from '@angular/material/checkbox';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
@@ -41,7 +41,6 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
imports: [
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
MatCardTitle,
|
||||
MatCheckbox,
|
||||
MatIconButton,
|
||||
MatTooltip
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
/** Angular Imports */
|
||||
import { Component } from '@angular/core';
|
||||
import { MatTabNav, MatTabLink, MatTabNavPanel } from '@angular/material/tabs';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { RouterLinkActive, RouterLink, RouterOutlet } from '@angular/router';
|
||||
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
|
||||
@@ -26,8 +25,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
MatTabLink,
|
||||
RouterLinkActive,
|
||||
MatTabNavPanel,
|
||||
RouterOutlet,
|
||||
MatDivider
|
||||
RouterOutlet
|
||||
]
|
||||
})
|
||||
export class CheckerInboxAndTasksComponent {
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatIconButton } from '@angular/material/button';
|
||||
import { MatTooltip } from '@angular/material/tooltip';
|
||||
|
||||
/** Custom Services */
|
||||
import { UsersService } from '../users.service';
|
||||
@@ -35,8 +34,7 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
|
||||
...STANDALONE_SHARED_IMPORTS,
|
||||
FaIconComponent,
|
||||
MatMenuModule,
|
||||
MatIconButton,
|
||||
MatTooltip
|
||||
MatIconButton
|
||||
]
|
||||
})
|
||||
export class ViewUserComponent {
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
/* tslint:disable */
|
||||
export default {
|
||||
'mifos_x': {
|
||||
'version': '260507',
|
||||
'hash': 'a12ba20d'
|
||||
'version': '260508',
|
||||
'hash': '19c0e98e'
|
||||
},
|
||||
'allow_switching_backend_instance': true
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user