complete fd account module
This commit is contained in:
+52
-5
@@ -70,7 +70,7 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
/** Fixed Deposit Account Product Template */
|
/** Fixed Deposit Account Product Template */
|
||||||
fixedDepositsAccountProductTemplate: any;
|
fixedDepositsAccountProductTemplate: any;
|
||||||
/** Tenure selected from the interest rate chart step */
|
/** Tenure selected from the interest rate chart step */
|
||||||
selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number } | null = null;
|
selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number; depositAmount?: number } | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches FD account template from `resolve`
|
* Fetches FD account template from `resolve`
|
||||||
@@ -94,7 +94,7 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
this.fixedDepositsAccountProductTemplate = $event;
|
this.fixedDepositsAccountProductTemplate = $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTenureSelected($event: { depositPeriod: number; depositPeriodFrequencyId: number }) {
|
onTenureSelected($event: { depositPeriod: number; depositPeriodFrequencyId: number; depositAmount?: number }) {
|
||||||
this.selectedTenure = $event;
|
this.selectedTenure = $event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,12 +129,59 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
const locale = this.settingsService.language.code;
|
const locale = this.settingsService.language.code;
|
||||||
const dateFormat = this.settingsService.dateFormat;
|
const dateFormat = this.settingsService.dateFormat;
|
||||||
const monthDayFormat = 'dd MMMM';
|
const monthDayFormat = 'dd MMMM';
|
||||||
|
const pt = this.fixedDepositsAccountProductTemplate;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Deposit amount: Prefer user input from Rate Calculator, fallback to product template
|
||||||
|
if (this.selectedTenure?.depositAmount != null) {
|
||||||
|
terms.depositAmount = this.selectedTenure.depositAmount;
|
||||||
|
} else if (pt?.depositAmount != null) {
|
||||||
|
terms.depositAmount = pt.depositAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pt?.minDepositAmount != null) terms.minDepositAmount = pt.minDepositAmount;
|
||||||
|
if (pt?.maxDepositAmount != null) terms.maxDepositAmount = pt.maxDepositAmount;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Charges — from product template if not overridden in details
|
||||||
|
const rawCharges: any[] =
|
||||||
|
(this.fixedDepositAccount.charges?.length ? this.fixedDepositAccount.charges : pt?.charges) ?? [];
|
||||||
|
|
||||||
const fixedDepositAccount = {
|
const fixedDepositAccount = {
|
||||||
...this.fixedDepositAccount,
|
...this.fixedDepositAccount,
|
||||||
|
...terms,
|
||||||
|
...settings,
|
||||||
...(this.selectedTenure ?? {}),
|
...(this.selectedTenure ?? {}),
|
||||||
clientId: this.fixedDepositsAccountTemplate.clientId,
|
clientId: this.fixedDepositsAccountTemplate.clientId,
|
||||||
charges: this.fixedDepositAccount.charges.map((charge: any) => ({
|
charges: rawCharges.map((charge: any) => ({
|
||||||
chargeId: charge.id,
|
chargeId: charge.chargeId ?? charge.id,
|
||||||
amount: charge.amount,
|
amount: charge.amount,
|
||||||
dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat),
|
dueDate: charge.dueDate && this.dateUtils.formatDate(charge.dueDate, dateFormat),
|
||||||
feeOnMonthDay:
|
feeOnMonthDay:
|
||||||
@@ -142,7 +189,7 @@ export class CreateFixedDepositAccountComponent {
|
|||||||
feeInterval: charge.feeInterval
|
feeInterval: charge.feeInterval
|
||||||
})),
|
})),
|
||||||
submittedOnDate: this.dateUtils.formatDate(this.fixedDepositAccount.submittedOnDate, dateFormat),
|
submittedOnDate: this.dateUtils.formatDate(this.fixedDepositAccount.submittedOnDate, dateFormat),
|
||||||
charts: [{ chartSlabs: this.fixedDepositsAccountProductTemplate.accountChart?.chartSlabs ?? [] }],
|
charts: [{ chartSlabs: pt?.accountChart?.chartSlabs ?? [] }],
|
||||||
dateFormat,
|
dateFormat,
|
||||||
monthDayFormat,
|
monthDayFormat,
|
||||||
locale
|
locale
|
||||||
|
|||||||
+2
-4
@@ -55,11 +55,9 @@
|
|||||||
<h4>Nomination Facility</h4>
|
<h4>Nomination Facility</h4>
|
||||||
<p>Allow account holders to designate individuals to receive funds in the event of account holder's death.</p>
|
<p>Allow account holders to designate individuals to receive funds in the event of account holder's death.</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="facc-yn" [class.facc-yn--yes]="fixedDepositsAccountProductTemplate?.nominationFacility">
|
<mat-slide-toggle [(ngModel)]="nominationFacility" color="primary"></mat-slide-toggle>
|
||||||
{{ fixedDepositsAccountProductTemplate?.nominationFacility ? 'Yes' : 'No' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
@if (fixedDepositsAccountProductTemplate?.nominationFacility) {
|
@if (nominationFacility) {
|
||||||
<mifosx-nominee-form (nomineeChange)="onNomineeChange($event)"></mifosx-nominee-form>
|
<mifosx-nominee-form (nomineeChange)="onNomineeChange($event)"></mifosx-nominee-form>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+3
@@ -31,6 +31,7 @@ export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
|
|||||||
nomineeData: any = null;
|
nomineeData: any = null;
|
||||||
|
|
||||||
autoDebitFacility = false;
|
autoDebitFacility = false;
|
||||||
|
nominationFacility = false;
|
||||||
|
|
||||||
// Editable feature toggles (pre-filled from product, overridable by user)
|
// Editable feature toggles (pre-filled from product, overridable by user)
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
|
|||||||
if (changes['fixedDepositsAccountProductTemplate']?.currentValue) {
|
if (changes['fixedDepositsAccountProductTemplate']?.currentValue) {
|
||||||
const pt = changes['fixedDepositsAccountProductTemplate'].currentValue;
|
const pt = changes['fixedDepositsAccountProductTemplate'].currentValue;
|
||||||
this.autoDebitFacility = !!pt.autoDebitFacility;
|
this.autoDebitFacility = !!pt.autoDebitFacility;
|
||||||
|
this.nominationFacility = !!pt.nominationFacility;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +51,7 @@ export class FixedDepositAccountCurrencyStepComponent implements OnChanges {
|
|||||||
get currencyStepData() {
|
get currencyStepData() {
|
||||||
return {
|
return {
|
||||||
autoDebitFacility: this.autoDebitFacility,
|
autoDebitFacility: this.autoDebitFacility,
|
||||||
|
nominationFacility: this.nominationFacility,
|
||||||
...(this.nomineeData ? { nominee: this.nomineeData } : {})
|
...(this.nomineeData ? { nominee: this.nomineeData } : {})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+62
-31
@@ -145,24 +145,23 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Principal Amount (SPECIAL only) -->
|
<!-- Principal Amount (all FD types) -->
|
||||||
@if (isSpecial) {
|
<div class="fdrc-calc-field">
|
||||||
<div class="fdrc-calc-field">
|
<label class="fdrc-calc-label">
|
||||||
<label class="fdrc-calc-label"
|
Deposit Amount
|
||||||
>Principal Amount <span class="fdrc-calc-optional">(optional — for monthly payout estimate)</span></label
|
<span class="fdrc-calc-optional">(optional — to calculate maturity amount)</span>
|
||||||
>
|
</label>
|
||||||
<div class="fdrc-amount-row">
|
<div class="fdrc-amount-row">
|
||||||
<span class="fdrc-amount-prefix">₹</span>
|
<span class="fdrc-amount-prefix">₹</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
class="fdrc-tenure-input fdrc-tenure-input--amount"
|
class="fdrc-tenure-input fdrc-tenure-input--amount"
|
||||||
[(ngModel)]="principalAmount"
|
[(ngModel)]="principalAmount"
|
||||||
[min]="1"
|
[min]="1"
|
||||||
placeholder="e.g. 100000"
|
placeholder="e.g. 100000"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
</div>
|
||||||
|
|
||||||
<div class="fdrc-calc-actions">
|
<div class="fdrc-calc-actions">
|
||||||
<button type="button" class="fdrc-calc-btn" (click)="calculateRate()">
|
<button type="button" class="fdrc-calc-btn" (click)="calculateRate()">
|
||||||
@@ -237,25 +236,57 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Monthly Payout (SPECIAL) -->
|
<!-- Maturity Summary (all FD types) -->
|
||||||
@if (isSpecial && matchResult.estimatedMonthlyPayout != null) {
|
@if (matchResult.maturityAmount != null) {
|
||||||
<div class="fdrc-payout-card">
|
<div class="fdrc-maturity-card">
|
||||||
<i class="fas fa-wallet"></i>
|
<div class="fdrc-maturity-card__header">
|
||||||
<div class="fdrc-payout-card__content">
|
<i class="fas fa-coins"></i>
|
||||||
<span class="fdrc-payout-card__label">Estimated Monthly Payout</span>
|
<span>Maturity Summary</span>
|
||||||
<span class="fdrc-payout-card__amount"
|
</div>
|
||||||
>₹ {{ matchResult.estimatedMonthlyPayout | number: '1.2-2' }}</span
|
<div class="fdrc-maturity-card__body">
|
||||||
>
|
<div class="fdrc-maturity-row">
|
||||||
<span class="fdrc-payout-card__formula"
|
<span class="fdrc-maturity-row__label">Deposit Amount</span>
|
||||||
>= ₹{{ principalAmount | number }} × {{ matchResult.appliedRate }}% ÷ 12</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">Interest Earned</span>
|
||||||
|
<span class="fdrc-maturity-row__value">₹ {{ matchResult.interestEarned | number: '1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
@if (isSpecial && matchResult.estimatedMonthlyPayout != null) {
|
||||||
|
<div class="fdrc-maturity-row">
|
||||||
|
<span class="fdrc-maturity-row__label">Monthly Payout</span>
|
||||||
|
<span class="fdrc-maturity-row__value fdrc-maturity-row__value--highlight">
|
||||||
|
₹ {{ matchResult.estimatedMonthlyPayout | number: '1.2-2' }} / month
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div class="fdrc-maturity-row fdrc-maturity-row--total">
|
||||||
|
<span class="fdrc-maturity-row__label">
|
||||||
|
@if (isSpecial) {
|
||||||
|
Principal at Maturity
|
||||||
|
} @else {
|
||||||
|
Maturity Amount
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="fdrc-maturity-row__value fdrc-maturity-row__value--big">
|
||||||
|
₹ {{ (isSpecial ? principalAmount : matchResult.maturityAmount) | number: '1.2-2' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (isSpecial && !matchResult.estimatedMonthlyPayout) {
|
@if (!matchResult.maturityAmount) {
|
||||||
<div class="fdrc-payout-hint">
|
<div class="fdrc-payout-hint">
|
||||||
<i class="fas fa-lightbulb"></i>
|
<i class="fas fa-lightbulb"></i>
|
||||||
Enter a principal amount above to see estimated monthly interest payout.
|
Enter a deposit amount above to see the maturity calculation.
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+76
@@ -869,3 +869,79 @@ $red-light: #fee2e2;
|
|||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Maturity Card ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
.fdrc-maturity-card {
|
||||||
|
margin-top: 14px;
|
||||||
|
border: 1.5px solid #86efac;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: #dcfce7;
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #166534;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body {
|
||||||
|
background: #f0fdf4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fdrc-maturity-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-bottom: 1px solid #d1fae5;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
color: #374151;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--interest {
|
||||||
|
.fdrc-maturity-row__value {
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--total {
|
||||||
|
background: #dcfce7;
|
||||||
|
border-top: 1.5px solid #86efac;
|
||||||
|
padding: 10px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value--big {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #15803d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value--highlight {
|
||||||
|
color: #0369a1;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+37
-6
@@ -33,6 +33,9 @@ export interface SlabMatchResult {
|
|||||||
slabIndex: number;
|
slabIndex: number;
|
||||||
appliedRate: number;
|
appliedRate: number;
|
||||||
estimatedMonthlyPayout?: number;
|
estimatedMonthlyPayout?: number;
|
||||||
|
maturityAmount?: number;
|
||||||
|
interestEarned?: number;
|
||||||
|
tenureDays?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -69,7 +72,11 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
|
|||||||
@Input() fixedDepositsAccountTemplate: any;
|
@Input() fixedDepositsAccountTemplate: any;
|
||||||
@Input() fixedDepositsAccountProductTemplate: any;
|
@Input() fixedDepositsAccountProductTemplate: any;
|
||||||
|
|
||||||
@Output() tenureSelected = new EventEmitter<{ depositPeriod: number; depositPeriodFrequencyId: number }>();
|
@Output() tenureSelected = new EventEmitter<{
|
||||||
|
depositPeriod: number;
|
||||||
|
depositPeriodFrequencyId: number;
|
||||||
|
depositAmount?: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
interestRateChartData: any[] = [];
|
interestRateChartData: any[] = [];
|
||||||
|
|
||||||
@@ -99,6 +106,10 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
|
|||||||
tenurePeriodUnit: FdPeriodUnit = 'Days';
|
tenurePeriodUnit: FdPeriodUnit = 'Days';
|
||||||
principalAmount: number | null = null;
|
principalAmount: number | null = null;
|
||||||
|
|
||||||
|
get showPrincipalInput(): boolean {
|
||||||
|
return true; // shown for all FD types
|
||||||
|
}
|
||||||
|
|
||||||
matchResult: SlabMatchResult | null = null;
|
matchResult: SlabMatchResult | null = null;
|
||||||
validationWarning: string | null = null;
|
validationWarning: string | null = null;
|
||||||
|
|
||||||
@@ -219,19 +230,38 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
|
|||||||
const result: SlabMatchResult = {
|
const result: SlabMatchResult = {
|
||||||
slab: matchedSlab,
|
slab: matchedSlab,
|
||||||
slabIndex: matchedIndex,
|
slabIndex: matchedIndex,
|
||||||
appliedRate: rate
|
appliedRate: rate,
|
||||||
|
tenureDays: enteredDays
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.isSpecial && this.principalAmount && this.principalAmount > 0) {
|
if (this.principalAmount && this.principalAmount > 0) {
|
||||||
result.estimatedMonthlyPayout = (this.principalAmount * rate) / 1200;
|
const p = this.principalAmount;
|
||||||
|
const r = rate / 100;
|
||||||
|
const years = enteredDays / 365;
|
||||||
|
const n = 4; // Quarterly compounding
|
||||||
|
|
||||||
|
if (this.isSpecial) {
|
||||||
|
// Monthly payout: simple interest paid monthly, principal returned at maturity
|
||||||
|
result.estimatedMonthlyPayout = (p * rate) / 1200;
|
||||||
|
result.interestEarned = result.estimatedMonthlyPayout * (enteredDays / 30);
|
||||||
|
result.maturityAmount = p; // principal returned at maturity
|
||||||
|
} else {
|
||||||
|
// Compound interest (Standard Quarterly)
|
||||||
|
result.maturityAmount = p * Math.pow(1 + r / n, n * years);
|
||||||
|
result.interestEarned = result.maturityAmount - p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.matchResult = result;
|
this.matchResult = result;
|
||||||
|
|
||||||
// Emit selected tenure to parent for backend submission
|
// Emit selected tenure and principal amount to parent for backend submission
|
||||||
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
|
const frequencyId = this.resolveFrequencyId(this.tenurePeriodUnit);
|
||||||
if (frequencyId !== null) {
|
if (frequencyId !== null) {
|
||||||
this.tenureSelected.emit({ depositPeriod: this.tenureValue, depositPeriodFrequencyId: frequencyId });
|
this.tenureSelected.emit({
|
||||||
|
depositPeriod: this.tenureValue,
|
||||||
|
depositPeriodFrequencyId: frequencyId,
|
||||||
|
depositAmount: this.principalAmount || undefined
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,6 +280,7 @@ export class FixedDepositAccountInterestRateChartStepComponent implements OnChan
|
|||||||
this.principalAmount = null;
|
this.principalAmount = null;
|
||||||
this.matchResult = null;
|
this.matchResult = null;
|
||||||
this.validationWarning = null;
|
this.validationWarning = null;
|
||||||
|
this.tenurePeriodUnit = this.allowedPeriodUnits[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
onPeriodUnitChange() {
|
onPeriodUnitChange() {
|
||||||
|
|||||||
+39
@@ -408,6 +408,45 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<!-- ══ 4. Maturity Summary ═══════════════════════════════════ -->
|
||||||
|
@if (maturitySummary) {
|
||||||
|
<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">Deposit Amount</span>
|
||||||
|
<span class="preview-value">{{ maturitySummary.depositAmount | formatNumber }} {{ currencyName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Applicable Interest Rate</span>
|
||||||
|
<span class="preview-value">{{ maturitySummary.interestRate | formatNumber }}%</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Tenure</span>
|
||||||
|
<span class="preview-value">{{ maturitySummary.tenureLabel }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field">
|
||||||
|
<span class="preview-label">Estimated Interest Earned</span>
|
||||||
|
<span class="preview-value" style="color: var(--theme-success, #28a745); font-weight: 500">
|
||||||
|
+ {{ maturitySummary.totalInterest | formatNumber }} {{ currencyName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="preview-field" style="grid-column: span 2">
|
||||||
|
<span class="preview-label">Projected Maturity Amount</span>
|
||||||
|
<span class="preview-value preview-mono" style="font-weight: 600; font-size: 1.25rem">
|
||||||
|
{{ maturitySummary.maturityAmount | formatNumber }} {{ currencyName }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<!-- ══ 5. Interest Rate Chart ═══════════════════════════════════ -->
|
<!-- ══ 5. Interest Rate Chart ═══════════════════════════════════ -->
|
||||||
<div class="preview-section">
|
<div class="preview-section">
|
||||||
<div class="preview-section__header">
|
<div class="preview-section__header">
|
||||||
|
|||||||
+133
-7
@@ -77,7 +77,8 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
/** Fixed Deposits Account Data */
|
/** Fixed Deposits Account Data */
|
||||||
@Input() fixedDepositAccountData: any;
|
@Input() fixedDepositAccountData: any;
|
||||||
/** Tenure selected from the interest rate chart step */
|
/** Tenure selected from the interest rate chart step */
|
||||||
@Input() selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number } | null = null;
|
@Input() selectedTenure: { depositPeriod: number; depositPeriodFrequencyId: number; depositAmount?: number } | null =
|
||||||
|
null;
|
||||||
|
|
||||||
/** Charges Displayed Columns */
|
/** Charges Displayed Columns */
|
||||||
chargesDisplayedColumns: string[] = [
|
chargesDisplayedColumns: string[] = [
|
||||||
@@ -146,10 +147,27 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get matchedSlabIndex(): number {
|
get matchedSlabIndex(): number {
|
||||||
if (!this.selectedTenure || !this.interestRateChartData?.length) return -1;
|
if (!this.interestRateChartData?.length) return -1;
|
||||||
const { depositPeriod, depositPeriodFrequencyId } = this.selectedTenure;
|
|
||||||
|
let depositPeriod: number;
|
||||||
|
let depositPeriodFrequencyId: number;
|
||||||
|
|
||||||
|
if (this.selectedTenure) {
|
||||||
|
depositPeriod = this.selectedTenure.depositPeriod;
|
||||||
|
depositPeriodFrequencyId = this.selectedTenure.depositPeriodFrequencyId;
|
||||||
|
} else if (
|
||||||
|
this.fixedDepositsAccountProductTemplate?.minDepositTerm != null &&
|
||||||
|
this.fixedDepositsAccountProductTemplate?.minDepositTermType?.id != null
|
||||||
|
) {
|
||||||
|
depositPeriod = this.fixedDepositsAccountProductTemplate.minDepositTerm;
|
||||||
|
depositPeriodFrequencyId = this.fixedDepositsAccountProductTemplate.minDepositTermType.id;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Convert selected tenure to days for comparison
|
// Convert selected tenure to days for comparison
|
||||||
const toDays = (v: number, id: number) => {
|
const toDays = (v: number, id: number) => {
|
||||||
|
if (id === 1) return v * 7; // Weeks
|
||||||
if (id === 2) return v * 30; // Months
|
if (id === 2) return v * 30; // Months
|
||||||
if (id === 3) return v * 365; // Years
|
if (id === 3) return v * 365; // Years
|
||||||
return v; // Days (id=0)
|
return v; // Days (id=0)
|
||||||
@@ -158,7 +176,13 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
|
|
||||||
return this.interestRateChartData.findIndex((slab: any) => {
|
return this.interestRateChartData.findIndex((slab: any) => {
|
||||||
const periodTypeValue = (slab.periodType?.value || '').toLowerCase();
|
const periodTypeValue = (slab.periodType?.value || '').toLowerCase();
|
||||||
const slabId = periodTypeValue.includes('month') ? 2 : periodTypeValue.includes('year') ? 3 : 0;
|
const slabId = periodTypeValue.includes('month')
|
||||||
|
? 2
|
||||||
|
: periodTypeValue.includes('year')
|
||||||
|
? 3
|
||||||
|
: periodTypeValue.includes('week')
|
||||||
|
? 1
|
||||||
|
: 0;
|
||||||
const fromDays = toDays(slab.fromPeriod ?? 0, slabId);
|
const fromDays = toDays(slab.fromPeriod ?? 0, slabId);
|
||||||
const toDaysVal = slab.toPeriod != null ? toDays(slab.toPeriod, slabId) : Infinity;
|
const toDaysVal = slab.toPeriod != null ? toDays(slab.toPeriod, slabId) : Infinity;
|
||||||
return enteredDays >= fromDays && enteredDays <= toDaysVal;
|
return enteredDays >= fromDays && enteredDays <= toDaysVal;
|
||||||
@@ -166,9 +190,111 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get selectedTenureLabel(): string {
|
get selectedTenureLabel(): string {
|
||||||
if (!this.selectedTenure) return '';
|
let depositPeriod: number;
|
||||||
const { depositPeriod, depositPeriodFrequencyId } = this.selectedTenure;
|
let depositPeriodFrequencyId: number;
|
||||||
const unitMap: Record<number, string> = { 0: 'Days', 2: 'Months', 3: 'Years' };
|
|
||||||
|
if (this.selectedTenure) {
|
||||||
|
depositPeriod = this.selectedTenure.depositPeriod;
|
||||||
|
depositPeriodFrequencyId = this.selectedTenure.depositPeriodFrequencyId;
|
||||||
|
} else if (
|
||||||
|
this.fixedDepositsAccountProductTemplate?.minDepositTerm != null &&
|
||||||
|
this.fixedDepositsAccountProductTemplate?.minDepositTermType?.id != null
|
||||||
|
) {
|
||||||
|
depositPeriod = this.fixedDepositsAccountProductTemplate.minDepositTerm;
|
||||||
|
depositPeriodFrequencyId = this.fixedDepositsAccountProductTemplate.minDepositTermType.id;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const unitMap: Record<number, string> = { 0: 'Days', 1: 'Weeks', 2: 'Months', 3: 'Years' };
|
||||||
return `${depositPeriod} ${unitMap[depositPeriodFrequencyId] ?? ''}`;
|
return `${depositPeriod} ${unitMap[depositPeriodFrequencyId] ?? ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get maturitySummary(): {
|
||||||
|
depositAmount: number;
|
||||||
|
maturityAmount: number;
|
||||||
|
totalInterest: number;
|
||||||
|
interestRate: number;
|
||||||
|
tenureLabel: string;
|
||||||
|
} {
|
||||||
|
let depositPeriod = 0;
|
||||||
|
let depositPeriodFrequencyId = 0;
|
||||||
|
|
||||||
|
if (this.selectedTenure) {
|
||||||
|
depositPeriod = this.selectedTenure.depositPeriod;
|
||||||
|
depositPeriodFrequencyId = this.selectedTenure.depositPeriodFrequencyId;
|
||||||
|
} else if (
|
||||||
|
this.fixedDepositsAccountProductTemplate?.minDepositTerm != null &&
|
||||||
|
this.fixedDepositsAccountProductTemplate?.minDepositTermType?.id != null
|
||||||
|
) {
|
||||||
|
depositPeriod = this.fixedDepositsAccountProductTemplate.minDepositTerm;
|
||||||
|
depositPeriodFrequencyId = this.fixedDepositsAccountProductTemplate.minDepositTermType.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const depositAmount =
|
||||||
|
this.selectedTenure?.depositAmount ??
|
||||||
|
this.fixedDepositAccountData?.depositAmount ??
|
||||||
|
this.fixedDepositsAccountProductTemplate?.depositAmount ??
|
||||||
|
0;
|
||||||
|
|
||||||
|
const chartRate =
|
||||||
|
this.matchedSlabIndex >= 0 ? this.interestRateChartData[this.matchedSlabIndex]?.annualInterestRate : null;
|
||||||
|
const interestRate = chartRate ?? this.fixedDepositsAccountProductTemplate?.nominalAnnualInterestRate ?? 0;
|
||||||
|
const r = interestRate / 100;
|
||||||
|
|
||||||
|
// Tenure in years (t)
|
||||||
|
let t = 0;
|
||||||
|
const daysInYear =
|
||||||
|
this.fixedDepositsAccountProductTemplate?.interestCalculationDaysInYearType?.id === 360 ? 360 : 365;
|
||||||
|
|
||||||
|
if (depositPeriodFrequencyId === 0) {
|
||||||
|
// Days
|
||||||
|
t = depositPeriod / daysInYear;
|
||||||
|
} else if (depositPeriodFrequencyId === 1) {
|
||||||
|
// Weeks
|
||||||
|
t = (depositPeriod * 7) / daysInYear;
|
||||||
|
} else if (depositPeriodFrequencyId === 2) {
|
||||||
|
// Months
|
||||||
|
t = depositPeriod / 12;
|
||||||
|
} else if (depositPeriodFrequencyId === 3) {
|
||||||
|
// Years
|
||||||
|
t = depositPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compounding frequency (n)
|
||||||
|
const compPeriodValue = (
|
||||||
|
this.fixedDepositsAccountProductTemplate?.interestCompoundingPeriodType?.value || ''
|
||||||
|
).toLowerCase();
|
||||||
|
let n = 1;
|
||||||
|
if (compPeriodValue.includes('daily')) n = daysInYear;
|
||||||
|
else if (compPeriodValue.includes('monthly')) n = 12;
|
||||||
|
else if (compPeriodValue.includes('quarterly')) n = 4;
|
||||||
|
else if (compPeriodValue.includes('half')) n = 2;
|
||||||
|
else if (compPeriodValue.includes('biannual')) n = 2;
|
||||||
|
else if (compPeriodValue.includes('annual')) n = 1;
|
||||||
|
|
||||||
|
// Interest Calculation Type
|
||||||
|
const calcTypeValue = (
|
||||||
|
this.fixedDepositsAccountProductTemplate?.interestCalculationType?.value || ''
|
||||||
|
).toLowerCase();
|
||||||
|
const isSimple = calcTypeValue.includes('simple');
|
||||||
|
|
||||||
|
let maturityAmount = 0;
|
||||||
|
if (isSimple) {
|
||||||
|
maturityAmount = depositAmount + depositAmount * r * t;
|
||||||
|
} else {
|
||||||
|
// Compound interest: A = P(1 + r/n)^(nt)
|
||||||
|
maturityAmount = depositAmount * Math.pow(1 + r / n, n * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalInterest = maturityAmount - depositAmount;
|
||||||
|
|
||||||
|
return {
|
||||||
|
depositAmount,
|
||||||
|
maturityAmount,
|
||||||
|
totalInterest,
|
||||||
|
interestRate,
|
||||||
|
tenureLabel: this.selectedTenureLabel
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,13 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN!
|
// IMPORTANT: THIS FILE IS AUTO GENERATED! DO NOT MANUALLY EDIT OR CHECKIN!
|
||||||
/* tslint:disable */
|
/* tslint:disable */
|
||||||
export default {
|
export default {
|
||||||
'mifos_x': {
|
'mifos_x': {
|
||||||
'version': '260507',
|
'version': '260507',
|
||||||
'hash': 'b0cffd38'
|
'hash': 'a12ba20d'
|
||||||
},
|
},
|
||||||
'allow_switching_backend_instance': true
|
'allow_switching_backend_instance': true
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user