+ Principal + all payouts
+ ₹ {{ specialPreview.totalValueAtMaturity | number: '1.2-2' }}
+
+ }
+
+ @if (isSpecial && specialPreview) {
+
+
+ Monthly Interest Payout Schedule
+
+
+
+ From {{ specialPreview.startDate | date: 'dd MMM yyyy' }} (the day the account is opened) to
+ {{ specialPreview.maturityDate | date: 'dd MMM yyyy' }}, worked out by the account engine from the
+ product.
+
- Projected Maturity Amount
+ {{ isSpecial ? 'Principal at Maturity' : 'Projected Maturity Amount' }}
{{ maturitySummary.maturityAmount | formatNumber }} {{ currencyName }}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-preview-step/fixed-deposit-account-preview-step.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-preview-step/fixed-deposit-account-preview-step.component.ts
index fc8c3130a..4f90460f5 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-preview-step/fixed-deposit-account-preview-step.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-stepper/fixed-deposit-account-preview-step/fixed-deposit-account-preview-step.component.ts
@@ -145,6 +145,11 @@ export class FixedDepositAccountPreviewStepComponent implements OnChanges {
SPECIAL: 'FD Special (Monthly Interest Payout)'
};
+ /** FD Special pays its interest out monthly; the principal alone comes back at maturity. */
+ get isSpecial(): boolean {
+ return this.fixedDepositsAccountProductTemplate?.fdType === 'SPECIAL';
+ }
+
getFdTypeLabel(fdType: string): string {
return this.fdTypeLabels[fdType] || fdType;
}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component.html b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component.html
index de34802f2..5426c51bd 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component.html
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component.html
@@ -11,15 +11,25 @@
{{ 'labels.heading.Monthly Interest Payout Schedule' | translate }}
- {{ paidCount }} of {{ schedule.length }} periods paid so far
+
+ @if (isPreview) {
+ {{ schedule.length }} monthly payouts
+ } @else {
+ {{ paidCount }} of {{ schedule.length }} periods paid so far
+ }
+
- Computed by the actual interest engine for this account's full term -- the exact amount posted each month can vary
- slightly with the number of days in that month.
+ @if (data.note) {
+ {{ data.note }}
+ } @else {
+ Computed by the actual interest engine for this account's full term -- the exact amount posted each month can
+ vary slightly with the number of days in that month.
+ }
diff --git a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component.ts b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component.ts
index 612cc78f3..39ec30f18 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposit-account-view/custom-dialogs/interest-schedule-dialog/interest-schedule-dialog.component.ts
@@ -26,6 +26,9 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
* a 24+ month term makes for a long table that would otherwise push every other card on the tab down
* the page. Each row also shows whether that period's interest has actually posted yet, not just what
* the engine projects it to be.
+ *
+ * Also opened from the account-opening screen with `preview: true`: the backend's quote for an FD Special
+ * not opened yet, so there is no paid / pending status, and each period shows its number of days.
*/
@Component({
selector: 'mifosx-interest-schedule-dialog',
@@ -46,14 +49,22 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
export class InterestScheduleDialogComponent {
dialogRef = inject>(MatDialogRef);
data: {
- schedule: Array<{ fromDate: any; toDate: any; interestAmount: number; paid: boolean }>;
+ schedule: Array<{ fromDate: any; toDate: any; interestAmount: number; paid: boolean; days?: number }>;
currencyCode: string;
+ /** Account-opening quote: no paid / pending status. */
+ preview?: boolean;
+ /** Shown above the table instead of the account note. */
+ note?: string;
} = inject(MAT_DIALOG_DATA);
- get schedule(): Array<{ fromDate: any; toDate: any; interestAmount: number; paid: boolean }> {
+ get schedule(): Array<{ fromDate: any; toDate: any; interestAmount: number; paid: boolean; days?: number }> {
return this.data.schedule ?? [];
}
+ get isPreview(): boolean {
+ return !!this.data.preview;
+ }
+
get currencyCode(): string {
return this.data.currencyCode;
}
diff --git a/src/app/deposits/fixed-deposits/fixed-deposits.service.ts b/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
index e5af75631..fb2bb9c33 100644
--- a/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
+++ b/src/app/deposits/fixed-deposits/fixed-deposits.service.ts
@@ -154,6 +154,16 @@ export class FixedDepositsService {
return this.http.get(`/fixeddepositaccounts/calculate-fd-interest`, { params: httpParams });
}
+ /**
+ * FD quote while opening the account, for every FD type: the same body as create (clientId, productId,
+ * submittedOnDate, depositAmount, depositPeriod, depositPeriodFrequencyId, interest terms, senior citizen ...). The
+ * backend builds the would-be account from the product, without saving it, and returns the applied rate, maturity
+ * date and amount Submit would store, and the interest periods (FD Special: the monthly payout schedule).
+ */
+ accountPreview(body: any): Observable {
+ return this.http.post(`/fixeddepositaccounts/account-preview`, body);
+ }
+
/**
* FD Dashboard's "Renew" action on a matured FD whose product has a renewal grace period
* configured: backdated to the original maturity date within the grace window, or from today
diff --git a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.ts b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.ts
index d1abb4ad6..339a12d51 100644
--- a/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.ts
+++ b/src/app/products/fixed-deposit-products/fixed-deposit-product-stepper/fixed-deposit-product-terms-step/fixed-deposit-product-terms-step.component.ts
@@ -129,12 +129,24 @@ export class FixedDepositProductTermsStepComponent implements OnInit, OnChanges
* (see DepositAccountAssembler#assembleFrom on the backend), so the product itself is locked to the
* same values here to avoid an admin configuring a combination that the backend will silently override
* at account-creation time.
+ *
+ * Its interest is paid out every month, so nothing compounds on the principal: the Interest Calculation Type is
+ * locked to Simple as well. The backend refuses an FD Special product with any other value for these three
+ * (DepositProductDataValidator), so a request from anywhere else cannot save them either.
*/
private applyFdTypeConstraints() {
if (!this.fixedDepositProductTermsForm) return;
const postingCtrl = this.fixedDepositProductTermsForm.get('interestPostingPeriodType');
const compoundingCtrl = this.fixedDepositProductTermsForm.get('interestCompoundingPeriodType');
+ const calculationTypeCtrl = this.fixedDepositProductTermsForm.get('interestCalculateSelector');
if (this.fdType === 'SPECIAL') {
+ const simple = (this.interestCalculationTypeOptionData || []).find(
+ (o: any) => String(o.value ?? o.name ?? '').toLowerCase() === 'simple'
+ );
+ if (calculationTypeCtrl && simple) {
+ calculationTypeCtrl.setValue(simple.id);
+ calculationTypeCtrl.disable();
+ }
// Monthly = id 4 (MONTHLY in Fineract's SavingsPostingInterestPeriodType/SavingsCompoundingInterestPeriodType enums)
const findMonthly = (data: any) => data?.find((o: any) => o.value?.toLowerCase() === 'monthly' || o.id === 4);
if (postingCtrl && this.interestPostingPeriodTypeData) {
@@ -154,6 +166,7 @@ export class FixedDepositProductTermsStepComponent implements OnInit, OnChanges
} else {
postingCtrl?.enable();
compoundingCtrl?.enable();
+ calculationTypeCtrl?.enable();
}
}