diff --git a/src/app/products/loan-products/create-loan-product/create-loan-product.component.html b/src/app/products/loan-products/create-loan-product/create-loan-product.component.html
index 260e066d8..abc3da5c8 100644
--- a/src/app/products/loan-products/create-loan-product/create-loan-product.component.html
+++ b/src/app/products/loan-products/create-loan-product/create-loan-product.component.html
@@ -42,19 +42,94 @@
-
-
-
- {{ 'labels.inputs.SETTINGS' | translate }}
-
-
-
-
-
+ @if (isAdvanceProduct) {
+
+
+
+ SECURITY & LIEN
+
+
+
+
+
+
+
+
+
+ LIMIT & TENURE
+
+
+
+
+
+
+
+
+
+ INTEREST
+
+
+
+
+
+
+
+
+
+ PENAL & NPA
+
+
+
+
+
+
+
+
+
+ RENEWAL & CLOSURE
+
+
+
+
+
+
+ @if (isOverdraftFacility) {
+
+
+
+ CHEQUE BOOK
+
+
+
+
+
+ }
+ }
+
+ @if (!isAdvanceProduct) {
+
+
+
+ {{ 'labels.inputs.SETTINGS' | translate }}
+
+
+
+
+
+ }
@if (isAdvancedPaymentStrategy) {
@@ -90,15 +165,19 @@
}
-
-
-
- {{ 'labels.inputs.TERMS' | translate }}
-
-
-
-
-
+ @if (!isAdvanceProduct) {
+
+
+
+ {{ 'labels.inputs.TERMS' | translate }}
+
+
+
+
+
+ }
@@ -109,7 +188,7 @@
diff --git a/src/app/products/loan-products/create-loan-product/create-loan-product.component.ts b/src/app/products/loan-products/create-loan-product/create-loan-product.component.ts
index 93785c36a..50c14db3b 100644
--- a/src/app/products/loan-products/create-loan-product/create-loan-product.component.ts
+++ b/src/app/products/loan-products/create-loan-product/create-loan-product.component.ts
@@ -13,6 +13,12 @@ import { ActivatedRoute, Router } from '@angular/router';
/** Custom Components */
import { LoanProductDetailsStepComponent } from '../loan-product-stepper/loan-product-details-step/loan-product-details-step.component';
import { LoanProductCurrencyStepComponent } from '../loan-product-stepper/loan-product-currency-step/loan-product-currency-step.component';
+import { LoanProductSecurityLienStepComponent } from '../loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component';
+import { LoanProductAdvanceLimitTenureStepComponent } from '../loan-product-stepper/loan-product-advance-limit-tenure-step/loan-product-advance-limit-tenure-step.component';
+import { LoanProductAdvanceInterestStepComponent } from '../loan-product-stepper/loan-product-advance-interest-step/loan-product-advance-interest-step.component';
+import { LoanProductPenalNpaStepComponent } from '../loan-product-stepper/loan-product-penal-npa-step/loan-product-penal-npa-step.component';
+import { LoanProductRenewalClosureStepComponent } from '../loan-product-stepper/loan-product-renewal-closure-step/loan-product-renewal-closure-step.component';
+import { LoanProductChequeBookStepComponent } from '../loan-product-stepper/loan-product-cheque-book-step/loan-product-cheque-book-step.component';
import { LoanProductTermsStepComponent } from '../loan-product-stepper/loan-product-terms-step/loan-product-terms-step.component';
import { LoanProductSettingsStepComponent } from '../loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component';
import { LoanProductChargesStepComponent } from '../loan-product-stepper/loan-product-charges-step/loan-product-charges-step.component';
@@ -34,7 +40,7 @@ import {
import { Accounting } from 'app/core/utils/accounting';
import { StringEnumOptionData } from '../../../shared/models/option-data.model';
import { LoanProductDeferredIncomeRecognitionStepComponent } from '../loan-product-stepper/loan-product-capitalized-income-step/loan-product-deferred-income-recognition-step.component';
-import { UntypedFormGroup } from '@angular/forms';
+import { UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
import { LoanProductPaymentStrategyStepComponent } from '../loan-product-stepper/loan-product-payment-strategy-step/loan-product-payment-strategy-step.component';
import { LoanProductPreviewStepComponent } from '../loan-product-stepper/loan-product-preview-step/loan-product-preview-step.component';
@@ -51,6 +57,12 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
FaIconComponent,
LoanProductDetailsStepComponent,
LoanProductCurrencyStepComponent,
+ LoanProductSecurityLienStepComponent,
+ LoanProductAdvanceLimitTenureStepComponent,
+ LoanProductAdvanceInterestStepComponent,
+ LoanProductPenalNpaStepComponent,
+ LoanProductRenewalClosureStepComponent,
+ LoanProductChequeBookStepComponent,
LoanProductSettingsStepComponent,
LoanProductInterestRefundStepComponent,
LoanProductPaymentStrategyStepComponent,
@@ -73,12 +85,27 @@ export class CreateLoanProductComponent implements OnInit {
@ViewChild(LoanProductDetailsStepComponent, { static: true }) loanProductDetailsStep: LoanProductDetailsStepComponent;
@ViewChild(LoanProductCurrencyStepComponent, { static: true })
loanProductCurrencyStep: LoanProductCurrencyStepComponent;
+ // Conditional tabs (Advances A/C only): static false — resolved after the @if renders them.
+ @ViewChild(LoanProductSecurityLienStepComponent)
+ loanProductSecurityLienStep: LoanProductSecurityLienStepComponent;
+ @ViewChild(LoanProductAdvanceLimitTenureStepComponent)
+ loanProductAdvanceLimitTenureStep: LoanProductAdvanceLimitTenureStepComponent;
+ @ViewChild(LoanProductAdvanceInterestStepComponent)
+ loanProductAdvanceInterestStep: LoanProductAdvanceInterestStepComponent;
+ @ViewChild(LoanProductPenalNpaStepComponent) loanProductPenalNpaStep: LoanProductPenalNpaStepComponent;
+ @ViewChild(LoanProductRenewalClosureStepComponent)
+ loanProductRenewalClosureStep: LoanProductRenewalClosureStepComponent;
+ @ViewChild(LoanProductChequeBookStepComponent) loanProductChequeBookStep: LoanProductChequeBookStepComponent;
+
+ /** Charges step needs a real control; Settings is absent on the Advances path, so fall back to this. */
+ readonly multiDisburseFallback = new UntypedFormControl(false);
@ViewChild(LoanProductInterestRefundStepComponent, { static: true })
loanProductInterestRefundStep: LoanProductInterestRefundStepComponent;
@ViewChild(LoanProductDeferredIncomeRecognitionStepComponent, { static: true })
loanProductDeferredIncomeRecognitionStep: LoanProductDeferredIncomeRecognitionStepComponent;
- @ViewChild(LoanProductTermsStepComponent, { static: true }) loanProductTermsStep: LoanProductTermsStepComponent;
- @ViewChild(LoanProductSettingsStepComponent, { static: true })
+ // Terms & Settings tabs are now conditional (Loans path only), so resolve them non-statically.
+ @ViewChild(LoanProductTermsStepComponent) loanProductTermsStep: LoanProductTermsStepComponent;
+ @ViewChild(LoanProductSettingsStepComponent)
loanProductSettingsStep: LoanProductSettingsStepComponent;
@ViewChild(LoanProductChargesStepComponent, { static: true }) loanProductChargesStep: LoanProductChargesStepComponent;
@ViewChild(LoanProductAccountingStepComponent, { static: true })
@@ -143,7 +170,18 @@ export class CreateLoanProductComponent implements OnInit {
}
get loanProductTermsForm() {
- return this.loanProductTermsStep.loanProductTermsForm;
+ // Absent on the Advances path (tab hidden), so guard for callers.
+ return this.loanProductTermsStep?.loanProductTermsForm;
+ }
+
+ /** True once the Advance Limit & Tenure step reports an Overdraft (running) facility. */
+ get isOverdraftFacility(): boolean {
+ return this.isAdvanceProduct && !!this.loanProductAdvanceLimitTenureStep?.isOverdraft;
+ }
+
+ /** Sub type chosen on the Details step; drives the FD 12–15 month tenure policy downstream. */
+ get advanceSubType(): string {
+ return this.loanProductDetailsStep?.loanProductDetailsForm?.get('productTypeSubGroup')?.value ?? '';
}
advancePaymentStrategy(value: string) {
@@ -231,47 +269,71 @@ export class CreateLoanProductComponent implements OnInit {
}
get loanProductSettingsForm() {
- return this.loanProductSettingsStep.loanProductSettingsForm;
+ // Absent on the Advances path (tab hidden), so guard for callers.
+ return this.loanProductSettingsStep?.loanProductSettingsForm;
}
get loanProductAccountingForm() {
return this.loanProductAccountingStep.loanProductAccountingForm;
}
+ /** The Details step's chosen product type; drives the Advances-only tabs. */
+ get isAdvanceProduct(): boolean {
+ return this.loanProductDetailsForm?.get('productTypeGroup')?.value === 'advances';
+ }
+
get totalTabs(): number {
- let tabs = 6; // details, currency, settings, terms, charges, accounting
- if (this.isAdvancedPaymentStrategy) tabs += 3; // interest refund, payment allocation, deferred income
+ let tabs = 4; // details, currency, charges, accounting (always)
+ if (this.isAdvanceProduct) {
+ // security & lien, limit & tenure, interest, penal & npa, renewal & closure
+ tabs += 5;
+ if (this.isOverdraftFacility) tabs += 1; // cheque book
+ } else {
+ tabs += 2; // settings, terms
+ if (this.isAdvancedPaymentStrategy) tabs += 3; // interest refund, payment allocation, deferred income
+ }
if (this.loanProductFormValid) tabs += 1; // preview
return tabs;
}
get loanProductFormValid() {
- if (this.isAdvancedPaymentStrategy) {
+ const base =
+ this.loanProductDetailsForm.valid && this.loanProductCurrencyForm.valid && this.loanProductAccountingForm.valid;
+
+ // Advances A/C: the curated Figma steps gate the form; Settings/Terms tabs are hidden.
+ if (this.isAdvanceProduct) {
return (
- this.loanProductDetailsForm.valid &&
- this.loanProductCurrencyForm.valid &&
- this.loanProductTermsForm.valid &&
- this.loanProductSettingsForm.valid &&
- this.loanIncomeCapitalizationForm.valid &&
- this.loanProductAccountingForm.valid
- );
- } else {
- return (
- this.loanProductDetailsForm.valid &&
- this.loanProductCurrencyForm.valid &&
- this.loanProductTermsForm.valid &&
- this.loanProductSettingsForm.valid &&
- this.loanProductAccountingForm.valid
+ base &&
+ !!this.loanProductSecurityLienStep?.loanProductSecurityLienForm?.valid &&
+ !!this.loanProductAdvanceLimitTenureStep?.loanProductAdvanceLimitTenureForm?.valid &&
+ !!this.loanProductAdvanceInterestStep?.loanProductAdvanceInterestForm?.valid &&
+ !!this.loanProductPenalNpaStep?.loanProductPenalNpaForm?.valid &&
+ !!this.loanProductRenewalClosureStep?.loanProductRenewalClosureForm?.valid &&
+ (!this.isOverdraftFacility || !!this.loanProductChequeBookStep?.loanProductChequeBookForm?.valid)
);
}
+
+ // Loans A/C: original Settings + Terms path.
+ const loansValid = !!this.loanProductTermsForm?.valid && !!this.loanProductSettingsForm?.valid;
+ if (this.isAdvancedPaymentStrategy) {
+ return base && loansValid && !!this.loanIncomeCapitalizationForm?.valid;
+ }
+ return base && loansValid;
}
get loanProduct() {
+ // Advances A/C builds its own payload: the curated steps map onto Fineract's mandatory core fields
+ // (principal, tenure, rate, amortization defaults) plus the whitelisted satellite params.
+ if (this.isAdvanceProduct) {
+ return this.assembleAdvanceProduct();
+ }
+
+ // Loans A/C: original composition (Settings/Terms present).
const loanProduct = {
...this.loanProductDetailsStep.loanProductDetails,
...this.loanProductCurrencyStep.loanProductCurrency,
- ...this.loanProductTermsStep.loanProductTerms,
- ...this.loanProductSettingsStep.loanProductSettings,
+ ...(this.loanProductTermsStep?.loanProductTerms ?? {}),
+ ...(this.loanProductSettingsStep?.loanProductSettings ?? {}),
...this.loanProductChargesStep.loanProductCharges,
...this.loanProductAccountingStep.loanProductAccounting
};
@@ -304,6 +366,163 @@ export class CreateLoanProductComponent implements OnInit {
return loanProduct;
}
+ /**
+ * Builds the create payload for an Advances A/C product: the curated wizard steps mapped onto Fineract's
+ * mandatory core fields (so the standard product saves) plus the whitelisted satellite params (persisted by
+ * LoanProductAdvanceExtService). Only keys the backend accepts are emitted — an unknown key would be
+ * rejected by checkForUnsupportedParameters.
+ */
+ private assembleAdvanceProduct(): any {
+ const num = (v: any): number | undefined => (v === '' || v === null || v === undefined ? undefined : Number(v));
+
+ const d = this.loanProductDetailsStep.loanProductDetails;
+ const cur = this.loanProductCurrencyStep.loanProductCurrency;
+ const sec = this.loanProductSecurityLienStep.loanProductSecurityLien;
+ const lt = this.loanProductAdvanceLimitTenureStep.loanProductAdvanceLimitTenure;
+ const intr = this.loanProductAdvanceInterestStep.loanProductAdvanceInterest;
+ const pn = this.loanProductPenalNpaStep.loanProductPenalNpa;
+ const rc = this.loanProductRenewalClosureStep.loanProductRenewalClosure;
+ const cb = this.isOverdraftFacility ? this.loanProductChequeBookStep?.loanProductChequeBook : null;
+
+ const defaultStrategy = this.loanProductsTemplate?.transactionProcessingStrategyOptions?.[0]?.code;
+ const minTenure = num(lt.minTenure);
+ const minRate = num(intr.minRate);
+
+ const payload: any = {
+ // identity + currency
+ name: d.name,
+ shortName: d.shortName,
+ description: d.description,
+ startDate: d.startDate,
+ closeDate: d.closeDate,
+ currencyCode: cur.currencyCode,
+ digitsAfterDecimal: cur.digitsAfterDecimal,
+ inMultiplesOf: 1,
+
+ // limit -> principal
+ minPrincipal: num(lt.minLimit),
+ principal: num(lt.defaultLimit) ?? num(lt.minLimit),
+ maxPrincipal: num(lt.maxLimit),
+
+ // tenure -> repayments (monthly)
+ minNumberOfRepayments: minTenure,
+ numberOfRepayments: num(lt.defaultTenure) ?? minTenure,
+ maxNumberOfRepayments: num(lt.maxTenure),
+ repaymentEvery: 1,
+ repaymentFrequencyType: 2, // months
+
+ // rate
+ minInterestRatePerPeriod: minRate,
+ interestRatePerPeriod: minRate,
+ maxInterestRatePerPeriod: num(intr.maxRate),
+ interestRateFrequencyType: 3, // per year
+
+ // Fineract mandatory engine defaults for an Advance (no EMI schedule shown to the officer)
+ amortizationType: 1, // equal installments
+ interestType: 0, // declining balance
+ interestCalculationPeriodType: 1, // same as repayment period
+ transactionProcessingStrategyCode: defaultStrategy,
+ daysInYearType: 1, // actual
+ daysInMonthType: 1, // actual
+ isInterestRecalculationEnabled: false,
+
+ // NPA / delinquency map onto core product fields
+ overdueDaysForNPA: num(pn.npaDays),
+ graceOnArrearsAgeing: num(pn.graceOnArrearsAgeing),
+ inArrearsTolerance: num(pn.inArrearsTolerance),
+ delinquencyBucketId: pn.delinquencyBucketId || undefined,
+
+ // accounting rule + GL mappings + charges (all standard, whitelisted)
+ ...this.loanProductAccountingStep.loanProductAccounting,
+ ...this.loanProductChargesStep.loanProductCharges,
+
+ // ---- satellite params (must match LoanProductAdvanceExtService.ADVANCE_PARAMETERS) ----
+ productTypeGroup: d.productTypeGroup,
+ productTypeSubGroup: d.productTypeSubGroup,
+ glAccount: d.glAccount || undefined,
+ loanProductCategory: d.loanProductCategory,
+ prioritySector: d.prioritySector,
+ cibilAccountType: d.cibilAccountType,
+ kfsEnabled: d.kfsEnabled,
+ aprEnabled: d.aprEnabled,
+ facilityType: lt.facilityType,
+ securityType: sec.securityType,
+ eligibleAmountBasis: sec.eligibleAmountBasis,
+ margin: num(sec.margin),
+ ltv: sec.ltv,
+ dpMethod: 'security_value_x_ltv',
+ lienRequired: this.loanProductSecurityLienStep.isLienSubType,
+ lienMarkingStage: sec.lienMarkingStage,
+ lienCertificateRequired: sec.lienCertificateRequired,
+ lienCertificatePrefix: sec.lienCertificatePrefix,
+ printCertificateOnDisbursement: sec.printOnDisbursement,
+ interestBasis: intr.interestBasis,
+ spread: num(intr.spread),
+ interestChargedOn: intr.interestChargedOn,
+ interestPostingFrequency: intr.postingFrequency,
+ penalApplicable: pn.penalApplicable,
+ penalRate: num(pn.penalRate),
+ penalAppliedOn: pn.penalAppliedOn,
+ penalGraceDays: num(pn.penalGraceDays),
+ npaTrigger: pn.npaTrigger,
+ interestAfterNpa: pn.interestAfterNpa,
+ renewalAllowed: rc.renewalAllowed,
+ renewalPeriodMonths: num(rc.renewalPeriodMonths),
+ renewalGraceDays: num(rc.renewalGraceDays),
+ preclosureAllowed: rc.preclosureAllowed,
+ lockInMonths: num(rc.lockInMonths),
+ interestOnPreclosure: rc.interestOnPreclosure,
+ chequeBookEnabled: this.isOverdraftFacility,
+ chequeType: cb?.chequeType,
+ defaultChequeBookSize: num(cb?.defaultBookSize),
+ ppsThreshold: num(cb?.ppsThreshold),
+ ppsMandatoryAbove: num(cb?.ppsMandatoryAbove),
+
+ // ---- remaining wizard fields ----
+ // security & lien extras (deposit list joined; rule checkboxes)
+ eligibleDepositProducts: (sec.eligibleDepositProducts ?? []).join('|'),
+ lienTenureNotExceedMaturity: sec.lienRules?.tenureNotExceedMaturity,
+ lienAutoReleaseOnClosure: sec.lienRules?.autoReleaseOnClosure,
+ lienAllowMultipleDeposits: sec.lienRules?.allowMultipleDeposits,
+ lienAdjustFromMaturity: sec.lienRules?.adjustFromMaturity,
+ lienAllowThirdParty: sec.lienRules?.allowThirdPartyDeposit,
+ lienAllowRenewalUnderLien: sec.lienRules?.allowRenewalUnderLien,
+ // limit & tenure extras
+ repaymentType: lt.repaymentType,
+ appropriationOrder: lt.appropriationOrder,
+ debitBalanceAllow: lt.debitBalanceAllow,
+ allowDebitAfterDueDate: lt.allowDebitTransAfterDueDate,
+ branchLevelAccountType: lt.branchLevelAccountType,
+ // interest extras
+ interestCalcMethod: intr.calcMethod,
+ interestOnCreditBalance: intr.interestOnCreditBalance,
+ interestPostingDay: intr.postingDay,
+ // NPA upgrade rules
+ npaUpgradeOnlyAllArrears: pn.upgradeOnlyAllArrears,
+ npaKeepStandardIfDepositCovers: pn.keepStandardIfDepositCovers,
+ // cheque book full config
+ chequeBookSizes: (cb?.bookSizes ?? []).join('|'),
+ chequeMaxBooksOutstanding: num(cb?.maxBooksOutstanding),
+ chequeReissueWhenLeaves: num(cb?.reissueWhenLeaves),
+ chequeFirstBookFree: cb?.firstBookFree,
+ chequeFreeBooksPerYear: num(cb?.freeBooksPerYear),
+ chequeNamePrinted: cb?.namePrinted,
+ chequePrintDescription: cb?.chequePrintDescription,
+ chequeStopPaymentAllowed: cb?.stopPaymentAllowed,
+ chequeMaintainStockRegister: cb?.maintainStockRegister,
+ chequeAllowWithdrawalSlip: cb?.allowWithdrawalSlip,
+ chequeBlockNpa: cb?.blockNpa,
+ chequeBlockLimitExpired: cb?.blockLimitExpired,
+ chequeBlockOverDp: cb?.blockOverDp,
+ chequeBlockFrozen: cb?.blockFrozen,
+ chequeBlockLienReleased: cb?.blockLienReleased
+ };
+
+ // Drop undefined keys so we never send an unresolved value.
+ Object.keys(payload).forEach((k) => payload[k] === undefined && delete payload[k]);
+ return payload;
+ }
+
submit() {
const loanProduct = this.loanProducts.buildPayload(this.loanProduct, this.itemsByDefault);
if (this.isAdvancedPaymentStrategy) {
diff --git a/src/app/products/loan-products/loan-product-stepper/advance-steps.scss b/src/app/products/loan-products/loan-product-stepper/advance-steps.scss
new file mode 100644
index 000000000..29d9a6b7e
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/advance-steps.scss
@@ -0,0 +1,174 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ * Shared styling for the Advances A/C wizard steps (Security & Lien, Limit & Tenure,
+ * Interest, Penal & NPA, Renewal & Closure, Cheque Book).
+ */
+
+$accent: #0ea5e9;
+$border: #e2e8f0;
+$gray-50: #f8f9fa;
+$gray-200: #e8eaed;
+$gray-900: #202124;
+
+.adv-section {
+ border: 1px solid $border;
+ border-radius: 10px;
+ overflow: hidden;
+ background: #fff;
+
+ & + & {
+ margin-top: 16px;
+ }
+
+ &__header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 24px;
+ background: $gray-50;
+ border-bottom: 1px solid $gray-200;
+
+ i {
+ font-size: 14px;
+ color: $accent;
+ }
+
+ h3 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 600;
+ color: $gray-900;
+ }
+ }
+
+ &__hint {
+ margin-left: auto;
+ font-size: 12px;
+ color: #5f6368;
+ }
+}
+
+.adv-subsection {
+ padding: 16px 24px;
+
+ & + & {
+ border-top: 1px solid $border;
+ }
+}
+
+.adv-group {
+ margin: 0 0 12px;
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: #5f6368;
+}
+
+.adv-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 4px 16px;
+ padding: 16px 24px;
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.adv-grid--3 {
+ grid-template-columns: repeat(3, 1fr);
+
+ @media (width <= 900px) {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.adv-subsection .adv-grid {
+ padding: 0;
+}
+
+.adv-field {
+ width: 100%;
+
+ &--full {
+ grid-column: 1 / -1;
+ }
+}
+
+.adv-checks {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px 32px;
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.adv-type-options {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+ padding: 16px 24px 0;
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.adv-type-option {
+ border: 1px solid #cbd5e1;
+ border-radius: 8px;
+ padding: 10px 14px;
+
+ &__label {
+ display: block;
+ font-weight: 600;
+ color: $gray-900;
+ }
+
+ &__hint {
+ display: block;
+ font-size: 12px;
+ color: #5f6368;
+ }
+}
+
+.adv-note {
+ display: flex;
+ align-items: flex-start;
+ gap: 8px;
+ margin-top: 16px;
+ padding: 12px 24px;
+ border: 1px dashed $border;
+ border-radius: 10px;
+ font-size: 13px;
+ color: #5f6368;
+
+ i {
+ color: $accent;
+ margin-top: 2px;
+ }
+}
+
+.adv-info {
+ padding: 12px 16px;
+ border-radius: 8px;
+ background: #f0f9ff;
+ border: 1px solid #bae6fd;
+ font-size: 13px;
+ color: #075985;
+ line-height: 1.5;
+}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-advance-interest-step/loan-product-advance-interest-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-advance-interest-step/loan-product-advance-interest-step.component.html
new file mode 100644
index 000000000..d81f96fbf
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-advance-interest-step/loan-product-advance-interest-step.component.html
@@ -0,0 +1,120 @@
+
+
+
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-advance-interest-step/loan-product-advance-interest-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-advance-interest-step/loan-product-advance-interest-step.component.ts
new file mode 100644
index 000000000..89dbb9956
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-advance-interest-step/loan-product-advance-interest-step.component.ts
@@ -0,0 +1,105 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+/**
+ * Interest step for an Advances A/C product (Figma step 5). Interest basis + spread, rate bounds,
+ * daily calculation and monthly posting. UI-only for now — not sent in the create payload.
+ */
+@Component({
+ selector: 'mifosx-loan-product-advance-interest-step',
+ templateUrl: './loan-product-advance-interest-step.component.html',
+ styleUrls: ['../advance-steps.scss'],
+ imports: [...STANDALONE_SHARED_IMPORTS]
+})
+export class LoanProductAdvanceInterestStepComponent {
+ private formBuilder = inject(UntypedFormBuilder);
+
+ loanProductAdvanceInterestForm: UntypedFormGroup;
+
+ readonly interestBasisOptions = [
+ { id: 'deposit_spread', label: 'Deposit Rate + Spread' },
+ { id: 'fixed', label: 'Fixed rate' },
+ { id: 'floating', label: 'Floating (EBLR-linked)' }
+ ];
+
+ readonly calcMethodOptions = [
+ { id: 'daily', label: 'Daily product (daily outstanding)' },
+ { id: 'monthly', label: 'Monthly average balance' }
+ ];
+
+ readonly daysInYearOptions = [
+ 'Actual (365 / 366)',
+ '365',
+ '360'
+ ];
+ readonly daysInMonthOptions = [
+ 'Actual',
+ '30'
+ ];
+
+ readonly chargedOnOptions = [
+ { id: 'utilised', label: 'Utilised (debit) balance' },
+ { id: 'limit', label: 'Sanctioned limit' }
+ ];
+
+ readonly postingFrequencyOptions = [
+ 'Monthly',
+ 'Quarterly',
+ 'Half-yearly',
+ 'At Maturity'
+ ];
+ readonly postingDayOptions = [
+ 'Last day of month',
+ '1st of month',
+ 'Anniversary day'
+ ];
+ readonly yesNoOptions = [
+ 'YES',
+ 'NO'
+ ];
+
+ constructor() {
+ this.loanProductAdvanceInterestForm = this.formBuilder.group({
+ interestBasis: ['deposit_spread'],
+ spread: [
+ 2.0,
+ [
+ Validators.min(0),
+ Validators.max(20)
+ ]
+ ],
+ minRate: [
+ '',
+ Validators.required
+ ],
+ maxRate: [
+ '',
+ Validators.required
+ ],
+ calcMethod: ['daily'],
+ daysInYear: ['Actual (365 / 366)'],
+ daysInMonth: ['Actual'],
+ interestChargedOn: ['utilised'],
+ interestOnCreditBalance: ['NO'],
+ postingFrequency: ['Monthly'],
+ postingDay: ['Last day of month']
+ });
+ }
+
+ get showSpread(): boolean {
+ return this.loanProductAdvanceInterestForm.get('interestBasis').value === 'deposit_spread';
+ }
+
+ get loanProductAdvanceInterest() {
+ return this.loanProductAdvanceInterestForm.getRawValue();
+ }
+}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-advance-limit-tenure-step/loan-product-advance-limit-tenure-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-advance-limit-tenure-step/loan-product-advance-limit-tenure-step.component.html
new file mode 100644
index 000000000..ed9f6e540
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-advance-limit-tenure-step/loan-product-advance-limit-tenure-step.component.html
@@ -0,0 +1,155 @@
+
+
+
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-advance-limit-tenure-step/loan-product-advance-limit-tenure-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-advance-limit-tenure-step/loan-product-advance-limit-tenure-step.component.ts
new file mode 100644
index 000000000..b35557303
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-advance-limit-tenure-step/loan-product-advance-limit-tenure-step.component.ts
@@ -0,0 +1,171 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, inject, Input } from '@angular/core';
+import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
+import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+/**
+ * Limit & Tenure step for an Advances A/C product (Figma step 4).
+ *
+ * Facility Type here decides the rest of the flow: an Overdraft (running) facility gets a cheque book,
+ * a Demand Loan does not. UI-only for now — not sent in the create payload.
+ */
+@Component({
+ selector: 'mifosx-loan-product-advance-limit-tenure-step',
+ templateUrl: './loan-product-advance-limit-tenure-step.component.html',
+ styleUrls: ['../advance-steps.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ MatRadioGroup,
+ MatRadioButton
+ ]
+})
+export class LoanProductAdvanceLimitTenureStepComponent {
+ private formBuilder = inject(UntypedFormBuilder);
+
+ loanProductAdvanceLimitTenureForm: UntypedFormGroup;
+
+ /**
+ * Product sub type picked on the Details step. "Loan against FD" is bank-policy capped to a
+ * 12–15 month tenure, so the officer cannot create an FD product outside that band. Other
+ * advance sub types (Gold, RD, CC, …) stay open.
+ */
+ private _productSubType = '';
+ @Input()
+ set productSubType(value: string) {
+ const next = value || '';
+ if (next === this._productSubType) {
+ return;
+ }
+ this._productSubType = next;
+ this.applyTenurePolicy();
+ }
+ get productSubType(): string {
+ return this._productSubType;
+ }
+
+ readonly repaymentTypeOptions = [
+ { id: 'bullet', label: 'Bullet – principal + interest at maturity' },
+ { id: 'running', label: 'Running – any time; clear by limit expiry' }
+ ];
+
+ readonly appropriationOrderOptions = [
+ { id: 'charges_penal_interest_principal', label: 'Charges → Penal → Interest → Principal' },
+ { id: 'penal_interest_principal_charges', label: 'Penal → Interest → Principal → Charges' }
+ ];
+
+ readonly yesNoOptions = [
+ 'YES',
+ 'NO'
+ ];
+
+ constructor() {
+ this.loanProductAdvanceLimitTenureForm = this.formBuilder.group({
+ facilityType: ['demand'],
+ minLimit: [
+ '',
+ Validators.required
+ ],
+ defaultLimit: [''],
+ maxLimit: [
+ '',
+ Validators.required
+ ],
+ minTenure: [
+ 12,
+ [
+ Validators.required,
+ Validators.min(1)
+ ]
+ ],
+ defaultTenure: [12],
+ maxTenure: [
+ 15,
+ [
+ Validators.required,
+ Validators.min(1)
+ ]
+ ],
+ repaymentType: ['bullet'],
+ appropriationOrder: ['charges_penal_interest_principal'],
+ debitBalanceAllow: ['YES'],
+ allowDebitTransAfterDueDate: ['NO'],
+ branchLevelAccountType: ['NO']
+ });
+ }
+
+ /** Read by the wizard to decide whether the Cheque Book step applies. */
+ get isOverdraft(): boolean {
+ return this.loanProductAdvanceLimitTenureForm.get('facilityType').value === 'overdraft';
+ }
+
+ get loanProductAdvanceLimitTenure() {
+ return this.loanProductAdvanceLimitTenureForm.getRawValue();
+ }
+
+ /** True when the FD 12–15 month cap is in force (drives the hints/errors in the template). */
+ get isFdTenureLocked(): boolean {
+ return this._productSubType === 'fd';
+ }
+
+ get tenureMin(): number {
+ return this.isFdTenureLocked ? 12 : 1;
+ }
+
+ get tenureMax(): number | null {
+ return this.isFdTenureLocked ? 15 : null;
+ }
+
+ /**
+ * FD advances are bank-policy capped to 12–15 months; every other sub type stays open (min 1).
+ * Called whenever the sub type changes so the validators — and thus the wizard's validity gate —
+ * match the selected product.
+ */
+ private applyTenurePolicy(): void {
+ const form = this.loanProductAdvanceLimitTenureForm;
+ if (!form) {
+ return;
+ }
+ const min = form.get('minTenure');
+ const def = form.get('defaultTenure');
+ const max = form.get('maxTenure');
+ if (this._productSubType === 'fd') {
+ const bounds = [
+ Validators.min(12),
+ Validators.max(15)
+ ];
+ min.setValidators([
+ Validators.required,
+ ...bounds
+ ]);
+ def.setValidators(bounds);
+ max.setValidators([
+ Validators.required,
+ ...bounds
+ ]);
+ min.setValue(12, { emitEvent: false });
+ def.setValue(12, { emitEvent: false });
+ max.setValue(15, { emitEvent: false });
+ } else {
+ min.setValidators([
+ Validators.required,
+ Validators.min(1)
+ ]);
+ def.setValidators([Validators.min(1)]);
+ max.setValidators([
+ Validators.required,
+ Validators.min(1)
+ ]);
+ }
+ min.updateValueAndValidity({ emitEvent: false });
+ def.updateValueAndValidity({ emitEvent: false });
+ max.updateValueAndValidity({ emitEvent: false });
+ }
+}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-cheque-book-step/loan-product-cheque-book-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-cheque-book-step/loan-product-cheque-book-step.component.html
new file mode 100644
index 000000000..cbac09492
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-cheque-book-step/loan-product-cheque-book-step.component.html
@@ -0,0 +1,144 @@
+
+
+
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-cheque-book-step/loan-product-cheque-book-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-cheque-book-step/loan-product-cheque-book-step.component.ts
new file mode 100644
index 000000000..3eade52c6
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-cheque-book-step/loan-product-cheque-book-step.component.ts
@@ -0,0 +1,92 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
+import { MatCheckbox } from '@angular/material/checkbox';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+/**
+ * Cheque Book step (Figma step 8), shown only when the Facility Type is Overdraft (running).
+ * UI-only for now — not sent in the create payload.
+ */
+@Component({
+ selector: 'mifosx-loan-product-cheque-book-step',
+ templateUrl: './loan-product-cheque-book-step.component.html',
+ styleUrls: ['../advance-steps.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ MatCheckbox
+ ]
+})
+export class LoanProductChequeBookStepComponent {
+ private formBuilder = inject(UntypedFormBuilder);
+
+ loanProductChequeBookForm: UntypedFormGroup;
+
+ readonly chequeTypeOptions = [
+ 'CTS-2010 · Payable at par (multi-city)',
+ 'CTS-2010 · Local',
+ 'Non-CTS'
+ ];
+ readonly bookSizeOptions = [
+ '10',
+ '25',
+ '50',
+ '100'
+ ];
+ readonly namePrintedOptions = [
+ 'Account holder name',
+ 'Firm / entity name',
+ 'As per instruction'
+ ];
+ readonly yesNoOptions = [
+ 'YES',
+ 'NO'
+ ];
+
+ constructor() {
+ this.loanProductChequeBookForm = this.formBuilder.group({
+ chequeType: ['CTS-2010 · Payable at par (multi-city)'],
+ bookSizes: [
+ [
+ '10',
+ '25',
+ '50'
+ ]
+ ],
+ defaultBookSize: ['25'],
+ maxBooksOutstanding: [1],
+ reissueWhenLeaves: [5],
+ firstBookFree: ['YES'],
+ freeBooksPerYear: [1],
+ namePrinted: ['Account holder name'],
+ chequePrintDescription: [''],
+ ppsThreshold: [
+ 50000,
+ [
+ Validators.required,
+ Validators.min(0)
+ ]
+ ],
+ ppsMandatoryAbove: [500000],
+ stopPaymentAllowed: ['YES'],
+ maintainStockRegister: ['YES'],
+ allowWithdrawalSlip: ['NO'],
+ blockNpa: [true],
+ blockLimitExpired: [true],
+ blockOverDp: [true],
+ blockFrozen: [true],
+ blockLienReleased: [true]
+ });
+ }
+
+ get loanProductChequeBook() {
+ return this.loanProductChequeBookForm.getRawValue();
+ }
+}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.html
index 49df9f6d2..62b792538 100644
--- a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.html
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.html
@@ -7,178 +7,190 @@
-->
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss
index f6a3f62d5..a5eecb505 100644
--- a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.scss
@@ -61,6 +61,57 @@ $gray-900: #202124;
&--full {
grid-column: 1 / -1;
}
+
+ // Starts a new row, so Start / Close Date sit together instead of one of them beside Short Name.
+ &--row-start {
+ grid-column-start: 1;
+ }
+}
+
+// Product Type — first section
+.lpd-type-options {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px;
+ padding: 16px 24px 0;
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.lpd-type-option {
+ border: 1px solid $border;
+ border-radius: 8px;
+ padding: 8px 12px;
+
+ &__label {
+ display: block;
+ font-weight: 600;
+ color: $gray-900;
+ }
+
+ &__hint {
+ display: block;
+ font-size: 12px;
+ color: #5f6368;
+ }
+}
+
+.lpd-type-pending {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 16px;
+ padding: 12px 24px;
+ border: 1px dashed $border;
+ border-radius: 10px;
+ font-size: 13px;
+ color: #5f6368;
+
+ i {
+ color: $accent;
+ }
}
.lpd-checkbox-row {
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.ts
index ded56834c..6b32897fd 100644
--- a/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.ts
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-details-step/loan-product-details-step.component.ts
@@ -15,6 +15,7 @@ import { Dates } from 'app/core/utils/dates';
import { SettingsService } from 'app/settings/settings.service';
import { MatTooltip } from '@angular/material/tooltip';
import { CdkTextareaAutosize } from '@angular/cdk/text-field';
+import { MatRadioButton, MatRadioGroup } from '@angular/material/radio';
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
@Component({
@@ -24,7 +25,9 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
imports: [
...STANDALONE_SHARED_IMPORTS,
MatTooltip,
- CdkTextareaAutosize
+ CdkTextareaAutosize,
+ MatRadioGroup,
+ MatRadioButton
]
})
export class LoanProductDetailsStepComponent implements OnInit {
@@ -50,6 +53,28 @@ export class LoanProductDetailsStepComponent implements OnInit {
}
ngOnInit() {
+ // Product Type is not persisted by the backend yet, so an existing product comes back without one.
+ // Only a new product is made to pick it first; editing must not hide or block the rest of the form.
+ this.isExistingProduct = !!this.loanProductsTemplate?.id;
+ if (!this.isExistingProduct) {
+ this.loanProductDetailsForm.get('productTypeGroup').setValidators(Validators.required);
+ this.loanProductDetailsForm.get('productTypeSubGroup').setValidators(Validators.required);
+ this.loanProductDetailsForm.get('glAccount').setValidators(Validators.required);
+ // A new product's short name is exactly two capital letters. Saved products keep the old 4-character
+ // rule, so an existing short name such as 'PW16' does not stop the product from being edited.
+ this.loanProductDetailsForm.get('shortName').setValidators([
+ Validators.required,
+ Validators.pattern(/^[A-Z]{2}$/)
+ ]);
+ this.loanProductDetailsForm.get('productTypeGroup').updateValueAndValidity({ emitEvent: false });
+ this.loanProductDetailsForm.get('productTypeSubGroup').updateValueAndValidity({ emitEvent: false });
+ this.loanProductDetailsForm.get('glAccount').updateValueAndValidity({ emitEvent: false });
+ this.loanProductDetailsForm.get('shortName').updateValueAndValidity({ emitEvent: false });
+ }
+
+ // A loan / advance is an asset of the bank, so its ledger GL is always an asset account.
+ this.assetAccountOptions = this.loanProductsTemplate?.accountingMappingOptions?.assetAccountOptions ?? [];
+
this.loanProductDetailsForm.patchValue({
name: this.loanProductsTemplate.name,
shortName: this.loanProductsTemplate.shortName,
@@ -57,83 +82,119 @@ export class LoanProductDetailsStepComponent implements OnInit {
startDate: this.loanProductsTemplate.startDate && new Date(this.loanProductsTemplate.startDate),
closeDate: this.loanProductsTemplate.closeDate && new Date(this.loanProductsTemplate.closeDate)
});
-
- const mappingOptions = this.loanProductsTemplate?.accountingMappingOptions ?? {};
- this.assetAccountOptions = mappingOptions.assetAccountOptions ?? [];
- this.liabilityAccountOptions = mappingOptions.liabilityAccountOptions ?? [];
-
- this.loanProductDetailsForm.patchValue({
- debitIntCalculation: this.loanProductsTemplate.debitIntCalculation ?? false,
- separatePenalInterest: this.loanProductsTemplate.separatePenalInterest ?? false,
- creditIntCalculation: this.loanProductsTemplate.creditIntCalculation ?? false,
- subAccountTypeBalance: this.loanProductsTemplate.subAccountTypeBalance ?? false,
- finalIntRounding: this.loanProductsTemplate.finalIntRounding ?? false,
- roundingUpTo: this.loanProductsTemplate.roundingUpTo ?? null
- });
}
- /** Product Classification data */
+ /** True when editing a saved product; see ngOnInit. */
+ isExistingProduct = false;
+
+ /** Product Type -- picked first, since it decides which sections the rest of the wizard shows. */
readonly productTypeGroups = [
- { id: 'loans', label: 'Loan' },
- { id: 'advanced', label: 'Advanced' }
+ { id: 'advances', label: 'Advances A/C', hint: 'No EMI. Limit / drawing power against security.' },
+ { id: 'loans', label: 'Loans A/C', hint: 'Fixed disbursement, fixed tenure, EMI repayment.' }
];
- readonly productTypeSubGroups: Record = {
- loans: [
- { id: 'emi', label: 'EMI' },
- { id: 'cc', label: 'CC (Cash Credit)' },
- { id: 'overdraft', label: 'Overdraft' },
- { id: 'warehouse', label: 'Warehouse' },
- { id: 'gold', label: 'Gold' }
+ /** `shortName` is the two-letter code the product's Short Name is pre-filled with when the sub type is picked. */
+ readonly productTypeSubGroups: Record = {
+ advances: [
+ { id: 'fd', label: 'Loan against FD', shortName: 'FD' },
+ { id: 'rd', label: 'Loan against RD', shortName: 'RD' },
+ { id: 'dds', label: 'Loan against DDS', shortName: 'DS' },
+ { id: 'lic', label: 'Loan against LIC Policy', shortName: 'LI' },
+ { id: 'gold', label: 'Loan against Gold', shortName: 'GD' },
+ { id: 'warehouse', label: 'Warehouse Receipt', shortName: 'WR' },
+ { id: 'cc', label: 'Cash Credit (CC)', shortName: 'CC' },
+ { id: 'overdraft', label: 'Overdraft (OD)', shortName: 'OD' },
+ { id: 'kcc', label: 'Kisan Credit Card (KCC)', shortName: 'KC' },
+ { id: 'bills', label: 'Bills Purchased / Discounted', shortName: 'BP' }
],
- advanced: [
- { id: 'fd', label: 'FD (Fixed Deposit)' },
- { id: 'rd', label: 'RD (Recurring Deposit)' }
+ loans: [
+ { id: 'term', label: 'Term Loan', shortName: 'TL' },
+ { id: 'personal', label: 'Personal Loan', shortName: 'PL' },
+ { id: 'housing', label: 'Housing Loan', shortName: 'HL' },
+ { id: 'vehicle', label: 'Vehicle Loan', shortName: 'VL' },
+ { id: 'education', label: 'Education Loan', shortName: 'EL' },
+ { id: 'business', label: 'Business / MSME Loan', shortName: 'BL' },
+ { id: 'gold_emi', label: 'Gold EMI Loan', shortName: 'GE' }
]
};
- /** Default GL per product type group */
- private readonly glDefaultMap: Record = {
- loans: 'asset',
- advanced: 'asset'
- };
+ /** Regulatory Classification options (UI-only; RBI / bureau reporting). */
+ readonly loanProductCategoryOptions = [
+ { id: 'advances_deposits', label: 'Advances against deposits' },
+ { id: 'gold_loan', label: 'Gold loan' },
+ { id: 'agriculture', label: 'Agriculture' },
+ { id: 'msme', label: 'MSME' },
+ { id: 'personal', label: 'Personal / retail' },
+ { id: 'housing', label: 'Housing' },
+ { id: 'other', label: 'Other' }
+ ];
+
+ readonly prioritySectorOptions = [
+ { id: 'no', label: 'No' },
+ { id: 'agriculture', label: 'Agriculture' },
+ { id: 'msme', label: 'MSME' },
+ { id: 'weaker_sections', label: 'Weaker Sections' },
+ { id: 'education', label: 'Education' },
+ { id: 'housing', label: 'Housing' }
+ ];
+
+ readonly cibilAccountTypeOptions = [
+ { id: 'loan_bank_deposits', label: 'Loan against Bank Deposits' },
+ { id: 'gold_loan', label: 'Gold Loan' },
+ { id: 'overdraft', label: 'Overdraft' },
+ { id: 'business_loan', label: 'Business Loan' },
+ { id: 'other', label: 'Other' }
+ ];
+
+ readonly yesNoOptions = [
+ 'YES',
+ 'NO'
+ ];
+
+ /** The rest of the Details form stays hidden on a new product until its type and sub type are picked. */
+ get isProductTypeChosen(): boolean {
+ if (this.isExistingProduct) {
+ return true;
+ }
+ const value = this.loanProductDetailsForm.value;
+ return !!value.productTypeGroup && !!value.productTypeSubGroup;
+ }
get availableSubGroups() {
const group = this.loanProductDetailsForm.get('productTypeGroup')?.value;
return group ? (this.productTypeSubGroups[group] ?? []) : [];
}
- get glOptions(): string[] {
- return [
- 'asset',
- 'liability'
- ];
- }
-
+ /**
+ * Asset GLs offered for the product's ledger GL -- the GL the product's loan account numbers are
+ * generated from. Separate from the Accounting tab mappings, which drive the journal entries.
+ */
assetAccountOptions: any[] = [];
- liabilityAccountOptions: any[] = [];
-
- get availableGlAccounts(): any[] {
- const type = this.loanProductDetailsForm.get('glClassification')?.value;
- if (type === 'asset') return this.assetAccountOptions;
- if (type === 'liability') return this.liabilityAccountOptions;
- return [];
- }
-
- get glClassificationLabel(): string {
- const type = this.loanProductDetailsForm.get('glClassification')?.value;
- return type === 'liability' ? 'Liability' : 'Asset';
- }
get selectedGroupLabel(): string {
const group = this.loanProductDetailsForm.get('productTypeGroup')?.value;
return this.productTypeGroups.find((g) => g.id === group)?.label ?? '';
}
- readonly yesNoOptions = [
- { value: true, label: 'YES' },
- { value: false, label: 'NO' }
- ];
+ /**
+ * A new product's short name is two capital letters, so what is typed or pasted is upper-cased, anything
+ * that is not a letter is dropped, and only the first two letters are kept. Trimming here rather than with
+ * maxlength means a paste like 'g1old' still yields 'GO' instead of being cut to 'g1' first.
+ */
+ normalizeShortName(event: Event) {
+ if (this.isExistingProduct) {
+ return;
+ }
+ // Read from the input itself: this handler can run before the form control has taken the new value.
+ const typed = (event.target as HTMLInputElement).value ?? '';
+ const cleaned = typed
+ .toUpperCase()
+ .replace(/[^A-Z]/g, '')
+ .slice(0, 2);
+ if (cleaned !== typed) {
+ this.loanProductDetailsForm.get('shortName').setValue(cleaned);
+ }
+ }
createLoanProductDetailsForm() {
this.loanProductDetailsForm = this.formBuilder.group({
@@ -150,21 +211,26 @@ export class LoanProductDetailsStepComponent implements OnInit {
closeDate: [''],
productTypeGroup: [''],
productTypeSubGroup: [''],
- glClassification: ['asset'],
glAccount: [''],
- debitIntCalculation: [false],
- separatePenalInterest: [false],
- creditIntCalculation: [false],
- subAccountTypeBalance: [false],
- finalIntRounding: [false],
- roundingUpTo: [null]
+ // Regulatory Classification (UI-only; stripped in buildPayload)
+ loanProductCategory: ['advances_deposits'],
+ prioritySector: ['no'],
+ cibilAccountType: ['loan_bank_deposits'],
+ kfsEnabled: ['YES'],
+ aprEnabled: ['YES']
});
- // Auto-set GL based on product type group
- this.loanProductDetailsForm.get('productTypeGroup')?.valueChanges.subscribe((group: string) => {
- const defaultVal = this.glDefaultMap[group] ?? 'asset';
- this.loanProductDetailsForm.get('glClassification')?.setValue(defaultVal);
- this.loanProductDetailsForm.get('glAccount')?.setValue('');
+ // A sub type belongs to one group, so changing the group clears it.
+ this.loanProductDetailsForm.get('productTypeGroup')?.valueChanges.subscribe(() => {
+ this.loanProductDetailsForm.get('productTypeSubGroup')?.setValue('');
+ });
+
+ // Picking a sub type pre-fills the Short Name with its code; the user can still change it.
+ this.loanProductDetailsForm.get('productTypeSubGroup')?.valueChanges.subscribe((subGroupId: string) => {
+ const subGroup = this.availableSubGroups.find((s) => s.id === subGroupId);
+ if (subGroup) {
+ this.loanProductDetailsForm.get('shortName').setValue(subGroup.shortName);
+ }
});
}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-penal-npa-step/loan-product-penal-npa-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-penal-npa-step/loan-product-penal-npa-step.component.html
new file mode 100644
index 000000000..30c8eda8e
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-penal-npa-step/loan-product-penal-npa-step.component.html
@@ -0,0 +1,106 @@
+
+
+
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-penal-npa-step/loan-product-penal-npa-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-penal-npa-step/loan-product-penal-npa-step.component.ts
new file mode 100644
index 000000000..0df4451de
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-penal-npa-step/loan-product-penal-npa-step.component.ts
@@ -0,0 +1,90 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, Input, inject } from '@angular/core';
+import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
+import { MatCheckbox } from '@angular/material/checkbox';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+/**
+ * Penal charges & asset-classification (SMA / NPA) step for an Advances A/C product (Figma step 6).
+ * UI-only for now — not sent in the create payload.
+ */
+@Component({
+ selector: 'mifosx-loan-product-penal-npa-step',
+ templateUrl: './loan-product-penal-npa-step.component.html',
+ styleUrls: ['../advance-steps.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ MatCheckbox
+ ]
+})
+export class LoanProductPenalNpaStepComponent {
+ private formBuilder = inject(UntypedFormBuilder);
+
+ @Input() loanProductsTemplate: any;
+
+ loanProductPenalNpaForm: UntypedFormGroup;
+
+ readonly yesNoOptions = [
+ 'YES',
+ 'NO'
+ ];
+
+ readonly appliedOnOptions = [
+ { id: 'overdue', label: 'Overdue amount' },
+ { id: 'outstanding', label: 'Full outstanding' }
+ ];
+
+ readonly npaTriggerOptions = [
+ { id: 'maturity', label: 'Dues overdue after maturity' },
+ { id: 'out_of_order', label: 'Out of order 90 days (CC/OD)' }
+ ];
+
+ readonly interestAfterNpaOptions = [
+ { id: 'undebited', label: 'Move to Undebited Interest GL' },
+ { id: 'suspense', label: 'Move to Interest Suspense GL' }
+ ];
+
+ get delinquencyBucketOptions(): any[] {
+ return this.loanProductsTemplate?.delinquencyBucketOptions ?? [];
+ }
+
+ constructor() {
+ this.loanProductPenalNpaForm = this.formBuilder.group({
+ penalApplicable: ['YES'],
+ penalRate: [
+ 2.0,
+ [
+ Validators.min(0),
+ Validators.max(100)
+ ]
+ ],
+ penalAppliedOn: ['overdue'],
+ penalGraceDays: [0],
+ delinquencyBucketId: [''],
+ npaDays: [
+ 90,
+ [
+ Validators.required,
+ Validators.min(0)
+ ]
+ ],
+ npaTrigger: ['maturity'],
+ interestAfterNpa: ['undebited'],
+ graceOnArrearsAgeing: [0],
+ inArrearsTolerance: [0],
+ upgradeOnlyAllArrears: [true],
+ keepStandardIfDepositCovers: [true]
+ });
+ }
+
+ get loanProductPenalNpa() {
+ return this.loanProductPenalNpaForm.getRawValue();
+ }
+}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-renewal-closure-step/loan-product-renewal-closure-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-renewal-closure-step/loan-product-renewal-closure-step.component.html
new file mode 100644
index 000000000..3a062eb6a
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-renewal-closure-step/loan-product-renewal-closure-step.component.html
@@ -0,0 +1,69 @@
+
+
+
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-renewal-closure-step/loan-product-renewal-closure-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-renewal-closure-step/loan-product-renewal-closure-step.component.ts
new file mode 100644
index 000000000..984ade3bc
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-renewal-closure-step/loan-product-renewal-closure-step.component.ts
@@ -0,0 +1,64 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, inject } from '@angular/core';
+import { UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+/**
+ * Limit renewal & pre-closure rules for an Advances A/C product (Figma step 7). Fees live on the
+ * Charges step. UI-only for now — not sent in the create payload.
+ */
+@Component({
+ selector: 'mifosx-loan-product-renewal-closure-step',
+ templateUrl: './loan-product-renewal-closure-step.component.html',
+ styleUrls: ['../advance-steps.scss'],
+ imports: [...STANDALONE_SHARED_IMPORTS]
+})
+export class LoanProductRenewalClosureStepComponent {
+ private formBuilder = inject(UntypedFormBuilder);
+
+ loanProductRenewalClosureForm: UntypedFormGroup;
+
+ readonly yesNoOptions = [
+ 'YES',
+ 'NO'
+ ];
+
+ readonly preclosureOptions = [
+ { id: 'anytime', label: 'Anytime' },
+ { id: 'after_lockin', label: 'After lock-in period' },
+ { id: 'not_allowed', label: 'Not allowed' }
+ ];
+
+ readonly preclosureInterestOptions = [
+ { id: 'till_closing', label: 'Till closing date (broken period)' },
+ { id: 'full_period', label: 'Full current period' }
+ ];
+
+ constructor() {
+ this.loanProductRenewalClosureForm = this.formBuilder.group({
+ renewalAllowed: ['YES'],
+ renewalPeriodMonths: [
+ 12,
+ [
+ Validators.required,
+ Validators.min(1)
+ ]
+ ],
+ renewalGraceDays: [30],
+ preclosureAllowed: ['anytime'],
+ lockInMonths: [0],
+ interestOnPreclosure: ['till_closing']
+ });
+ }
+
+ get loanProductRenewalClosure() {
+ return this.loanProductRenewalClosureForm.getRawValue();
+ }
+}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.html
new file mode 100644
index 000000000..c2410aea5
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.html
@@ -0,0 +1,160 @@
+
+
+
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.scss b/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.scss
new file mode 100644
index 000000000..7d57dfc5e
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.scss
@@ -0,0 +1,117 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+$accent: #0ea5e9;
+$border: #e2e8f0;
+$gray-50: #f8f9fa;
+$gray-200: #e8eaed;
+$gray-900: #202124;
+
+.lsl-section {
+ border: 1px solid $border;
+ border-radius: 10px;
+ overflow: hidden;
+ background: #fff;
+
+ & + & {
+ margin-top: 16px;
+ }
+
+ &__header {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 12px 24px;
+ background: $gray-50;
+ border-bottom: 1px solid $gray-200;
+
+ i {
+ font-size: 14px;
+ color: $accent;
+ }
+
+ h3 {
+ margin: 0;
+ font-size: 14px;
+ font-weight: 600;
+ color: $gray-900;
+ }
+ }
+
+ &__hint {
+ margin-left: auto;
+ font-size: 12px;
+ color: #5f6368;
+ }
+}
+
+.lsl-subsection {
+ padding: 16px 24px;
+
+ & + & {
+ border-top: 1px solid $border;
+ }
+}
+
+.lsl-group {
+ margin: 0 0 12px;
+ font-size: 12px;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ color: #5f6368;
+}
+
+.lsl-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 4px 16px;
+ padding: 16px 24px;
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.lsl-subsection .lsl-grid {
+ padding: 0;
+}
+
+.lsl-field {
+ width: 100%;
+
+ &--full {
+ grid-column: 1 / -1;
+ }
+}
+
+.lsl-checks {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 12px 32px;
+
+ @media (width <= 600px) {
+ grid-template-columns: 1fr;
+ }
+}
+
+.lsl-note {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-top: 16px;
+ padding: 12px 24px;
+ border: 1px dashed $border;
+ border-radius: 10px;
+ font-size: 13px;
+ color: #5f6368;
+
+ i {
+ color: $accent;
+ }
+}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.ts b/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.ts
new file mode 100644
index 000000000..d0327c94a
--- /dev/null
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-security-lien-step/loan-product-security-lien-step.component.ts
@@ -0,0 +1,196 @@
+/**
+ * Copyright since 2025 Mifos Initiative
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+import { Component, OnInit, Input, inject } from '@angular/core';
+import { AbstractControl, UntypedFormGroup, UntypedFormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
+import { MatTooltip } from '@angular/material/tooltip';
+import { MatCheckbox } from '@angular/material/checkbox';
+import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
+
+/**
+ * Security & Lien step, shown only for an "Advances A/C" product.
+ *
+ * For a deposit-backed advance (Loan against FD / RD / DDS / LIC) the bank marks a lien on the
+ * customer's own deposit. This step captures the product-level rules for that: the margin (from which
+ * the LTV and drawing power follow), which deposit products may be pledged, and the lien certificate.
+ *
+ * UI-only for now: its values are NOT sent in the create payload, because the backend has no column to
+ * store them yet. Once the satellite table lands, wire `loanProductSecurityLien` into the payload.
+ */
+@Component({
+ selector: 'mifosx-loan-product-security-lien-step',
+ templateUrl: './loan-product-security-lien-step.component.html',
+ styleUrls: ['./loan-product-security-lien-step.component.scss'],
+ imports: [
+ ...STANDALONE_SHARED_IMPORTS,
+ MatTooltip,
+ MatCheckbox
+ ]
+})
+export class LoanProductSecurityLienStepComponent implements OnInit {
+ private formBuilder = inject(UntypedFormBuilder);
+
+ @Input() loanProductsTemplate: any;
+ /** The Details step's Product Type controls, so this step reacts to the chosen group / sub type. */
+ @Input() productTypeGroup: AbstractControl;
+ @Input() productTypeSubGroup: AbstractControl;
+
+ loanProductSecurityLienForm: UntypedFormGroup;
+
+ /** Sub type -> the security it is backed by. Only deposit / policy sub types get a lien section. */
+ private readonly securityTypeBySubGroup: Record = {
+ fd: 'Fixed Deposit',
+ rd: 'Recurring Deposit',
+ dds: 'Daily Deposit Scheme',
+ lic: 'LIC Policy',
+ gold: 'Gold Ornaments',
+ warehouse: 'Warehouse Receipt',
+ cc: 'Stock / Book Debts',
+ overdraft: 'Deposit / Property'
+ };
+
+ /** Sub types whose security is lien-marked on a deposit (rather than pledged / hypothecated). */
+ private readonly lienSubGroups = [
+ 'fd',
+ 'rd',
+ 'dds',
+ 'lic'
+ ];
+
+ /** Default margin % per sub type (bank policy in real use); LTV is derived from it. */
+ private readonly defaultMarginBySubGroup: Record = {
+ fd: 10,
+ rd: 10,
+ dds: 10,
+ lic: 40
+ };
+
+ readonly eligibleAmountBasisOptions = [
+ { id: 'balance_accrued', label: 'Deposit balance + accrued interest' },
+ { id: 'balance', label: 'Deposit balance only' },
+ { id: 'surrender', label: 'Surrender value (LIC)' }
+ ];
+
+ readonly lienMarkingStageOptions = [
+ { id: 'disbursement', label: 'At Disbursement' },
+ { id: 'sanction', label: 'At Sanction' }
+ ];
+
+ readonly depositProductOptions = [
+ 'Fixed Deposit – Regular',
+ 'Fixed Deposit – Senior Citizen',
+ 'Recurring Deposit – Regular',
+ 'Daily Deposit Scheme'
+ ];
+
+ readonly yesNoOptions = [
+ 'YES',
+ 'NO'
+ ];
+
+ constructor() {
+ this.createForm();
+ }
+
+ ngOnInit() {
+ this.applySubGroup(this.productTypeSubGroup?.value);
+ // Keep Security Type and the default margin in step with the sub type chosen on the Details step.
+ this.productTypeSubGroup?.valueChanges.subscribe((subGroup: string) => this.applySubGroup(subGroup));
+ }
+
+ private applySubGroup(subGroup: string): void {
+ this.loanProductSecurityLienForm
+ .get('securityType')
+ .setValue(this.securityTypeBySubGroup[subGroup] ?? '', { emitEvent: false });
+ const margin = this.defaultMarginBySubGroup[subGroup];
+ if (margin != null && !this.loanProductSecurityLienForm.get('margin').dirty) {
+ this.loanProductSecurityLienForm.get('margin').setValue(margin, { emitEvent: false });
+ }
+ }
+
+ /** This whole step is only relevant to an Advances A/C product. */
+ get isAdvance(): boolean {
+ return this.productTypeGroup?.value === 'advances';
+ }
+
+ /** The lien block shows only for deposit / policy sub types; gold, warehouse, CC etc. are pledged. */
+ get isLienSubType(): boolean {
+ return this.lienSubGroups.includes(this.productTypeSubGroup?.value);
+ }
+
+ /** LTV = 100 − margin, shown read-only next to the margin. */
+ get ltv(): number | null {
+ const margin = this.loanProductSecurityLienForm.get('margin').value;
+ if (margin === null || margin === '' || isNaN(margin)) {
+ return null;
+ }
+ return 100 - Number(margin);
+ }
+
+ createForm() {
+ this.loanProductSecurityLienForm = this.formBuilder.group({
+ securityType: [{ value: '', disabled: true }],
+ eligibleAmountBasis: [
+ 'balance_accrued',
+ Validators.required
+ ],
+ margin: [
+ 10,
+ [
+ Validators.required,
+ Validators.min(0),
+ Validators.max(100)
+ ]
+ ],
+ lienMarkingStage: [
+ 'disbursement',
+ Validators.required
+ ],
+ eligibleDepositProducts: [
+ ['Fixed Deposit – Regular'],
+ Validators.required
+ ],
+ tenureNotExceedMaturity: [true],
+ autoReleaseOnClosure: [true],
+ allowMultipleDeposits: [false],
+ adjustFromMaturity: [true],
+ allowThirdPartyDeposit: [false],
+ allowRenewalUnderLien: [true],
+ lienCertificateRequired: ['YES'],
+ lienCertificatePrefix: [
+ 'LC/FD/',
+ Validators.required
+ ],
+ printOnDisbursement: ['YES']
+ });
+ }
+
+ /** Flat values for the payload. Not wired into submit yet — see the class comment. */
+ get loanProductSecurityLien() {
+ const value = this.loanProductSecurityLienForm.getRawValue();
+ return {
+ securityType: value.securityType,
+ eligibleAmountBasis: value.eligibleAmountBasis,
+ margin: value.margin,
+ ltv: this.ltv,
+ lienMarkingStage: value.lienMarkingStage,
+ eligibleDepositProducts: value.eligibleDepositProducts,
+ lienRules: {
+ tenureNotExceedMaturity: value.tenureNotExceedMaturity,
+ autoReleaseOnClosure: value.autoReleaseOnClosure,
+ allowMultipleDeposits: value.allowMultipleDeposits,
+ adjustFromMaturity: value.adjustFromMaturity,
+ allowThirdPartyDeposit: value.allowThirdPartyDeposit,
+ allowRenewalUnderLien: value.allowRenewalUnderLien
+ },
+ lienCertificateRequired: value.lienCertificateRequired,
+ lienCertificatePrefix: value.lienCertificatePrefix,
+ printOnDisbursement: value.printOnDisbursement
+ };
+ }
+}
diff --git a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.html b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.html
index 5853a8895..c255df105 100644
--- a/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.html
+++ b/src/app/products/loan-products/loan-product-stepper/loan-product-settings-step/loan-product-settings-step.component.html
@@ -7,1100 +7,1212 @@
-->