feat(loan-products): add Loans A/C extension fields (collateral, eligibility, repayment, rate source, prepayment)
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-5) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, build-core) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (main, cucumber) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 7 of 10) (push) Canceled after 0s
Fineract Build & Cucumber tests (without E2E tests) / verify (progressive-loan, build-progressive-loan) (push) Canceled after 0s
Fineract Docker Builds / build (false, docker-compose-postgresql.yml, postgresql) (push) Canceled after 0s
Fineract Docker Builds / build (true, docker-compose.yml, mariadb) (push) Canceled after 0s
Fineract Documentation build / build (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 1 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 10 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 2 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 3 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 4 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 5 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 6 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 of 10) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-2) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Fineract Liquibase Only mode - PostgreSQL / test (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s

Stores the Loans A/C (EMI) wizard's fields that have no home in
Fineract's core loan-product schema on the same per-product satellite
used for Advances: collateral/guarantor rules, borrower eligibility,
disbursement, repayment mode, rate source / floating reset, and
part-prepayment/foreclosure. Columns are loan_-prefixed so they never
collide with the Advances columns on the shared table.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 15:50:47 +05:30
co-authored by Claude Sonnet 5
parent 14973dfd5e
commit 2d10ec60f8
5 changed files with 452 additions and 2 deletions
@@ -295,6 +295,175 @@ public class LoanProductAdvanceExt {
@Column(name = "cheque_block_lien_released") @Column(name = "cheque_block_lien_released")
private Boolean chequeBlockLienReleased; private Boolean chequeBlockLienReleased;
// ---- Loans A/C (EMI) wizard fields -------------------------------------
@Column(name = "loan_security_mandatory")
private String securityMandatory;
@Column(name = "loan_guarantors_required")
private Integer guarantorsRequired;
@Column(name = "loan_co_applicant")
private String coApplicant;
@Column(name = "loan_third_party_security")
private String thirdPartySecurity;
@Column(name = "loan_legal_opinion_required")
private Boolean legalOpinionRequired;
@Column(name = "loan_property_insurance_required")
private Boolean propertyInsuranceRequired;
@Column(name = "loan_register_charge")
private Boolean registerCharge;
@Column(name = "loan_release_security_on_closure")
private Boolean releaseSecurityOnClosure;
@Column(name = "loan_allow_multiple_securities")
private Boolean allowMultipleSecurities;
@Column(name = "loan_allow_third_party_deposit")
private Boolean allowThirdPartyDeposit;
@Column(name = "loan_min_age")
private Integer minAge;
@Column(name = "loan_max_age_at_maturity")
private Integer maxAgeAtMaturity;
@Column(name = "loan_min_cibil")
private Integer minCibil;
@Column(name = "loan_min_monthly_income")
private BigDecimal minMonthlyIncome;
@Column(name = "loan_max_foir")
private BigDecimal maxFoir;
@Column(name = "loan_max_loans_per_customer")
private Integer maxLoansPerCustomer;
@Column(name = "loan_disbursement_type")
private String disbursementType;
@Column(name = "loan_disbursement_mode")
private String disbursementMode;
@Column(name = "loan_undisbursed_interest")
private String undisbursedInterest;
@Column(name = "loan_pre_emi_interest")
private Boolean preEmiInterest;
@Column(name = "loan_emi_due_day")
private String emiDueDay;
@Column(name = "loan_holiday_rule")
private String holidayRule;
@Column(name = "loan_moratorium_months")
private Integer moratoriumMonths;
@Column(name = "loan_moratorium_interest")
private String moratoriumInterest;
@Column(name = "loan_variable_installments")
private Boolean variableInstallments;
@Column(name = "loan_repayment_mode")
private String repaymentMode;
@Column(name = "loan_linked_account_type")
private String linkedAccountType;
@Column(name = "loan_pdc_collection")
private String pdcCollection;
@Column(name = "loan_mandate_required")
private Boolean mandateRequired;
@Column(name = "loan_advance_emi")
private String advanceEmi;
@Column(name = "loan_rate_type")
private String rateType;
@Column(name = "loan_register_particular")
private String registerParticular;
@Column(name = "loan_apply_cibil_adjustment")
private Boolean applyCibilAdjustment;
@Column(name = "loan_rate_resolved_at")
private String rateResolvedAt;
@Column(name = "loan_calc_period")
private String calcPeriod;
@Column(name = "loan_interest_starts_from")
private String interestStartsFrom;
@Column(name = "loan_compounding")
private String compounding;
@Column(name = "loan_benchmark")
private String benchmark;
@Column(name = "loan_reset_frequency")
private String resetFrequency;
@Column(name = "loan_reset_change")
private String resetChange;
@Column(name = "loan_part_prepayment_allowed")
private Boolean partPrepaymentAllowed;
@Column(name = "loan_min_part_payment")
private BigDecimal minPartPayment;
@Column(name = "loan_allowed_on")
private String allowedOn;
@Column(name = "loan_part_payments_per_year")
private String partPaymentsPerYear;
@Column(name = "loan_part_prepayment_mode")
private String partPrepaymentMode;
@Column(name = "loan_foreclosure_floating_individual")
private BigDecimal foreclosureFloatingIndividual;
@Column(name = "loan_foreclosure_fixed_individual")
private BigDecimal foreclosureFixedIndividual;
@Column(name = "loan_foreclosure_non_individual")
private BigDecimal foreclosureNonIndividual;
@Column(name = "loan_foreclosure_takeover")
private BigDecimal foreclosureTakeover;
@Column(name = "loan_foreclosure_quote_valid_days")
private Integer foreclosureQuoteValidDays;
@Column(name = "loan_auto_close_on_zero_balance")
private Boolean autoCloseOnZeroBalance;
@Column(name = "loan_noc_letter")
private String nocLetter;
@Column(name = "loan_restructuring")
private String restructuring;
@Column(name = "loan_release_collateral_on_closure")
private Boolean releaseCollateralOnClosure;
@Column(name = "loan_top_up_allowed")
private Boolean topUpAllowed;
@Column(name = "loan_balance_transfer_out_allowed")
private Boolean balanceTransferOutAllowed;
public LoanProductAdvanceExt(final LoanProduct loanProduct) { public LoanProductAdvanceExt(final LoanProduct loanProduct) {
this.loanProduct = loanProduct; this.loanProduct = loanProduct;
this.loanProductId = loanProduct.getId(); this.loanProductId = loanProduct.getId();
@@ -37,10 +37,15 @@ import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam; import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.core.UriInfo;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -58,6 +63,7 @@ import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum; import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
@@ -222,11 +228,54 @@ public class LoanProductsApiResource {
return this.productMixDataApiJsonSerializer.serialize(settings, productMixes, PRODUCT_MIX_DATA_PARAMETERS); return this.productMixDataApiJsonSerializer.serialize(settings, productMixes, PRODUCT_MIX_DATA_PARAMETERS);
} }
final Collection<LoanProductData> products = this.loanProductReadPlatformService.retrieveAllLoanProducts(); final Collection<LoanProductData> products = enrichAndFilterProducts(
this.loanProductReadPlatformService.retrieveAllLoanProducts(), uriInfo);
return this.toApiJsonSerializer.serialize(settings, products, LOAN_PRODUCT_DATA_PARAMETERS); return this.toApiJsonSerializer.serialize(settings, products, LOAN_PRODUCT_DATA_PARAMETERS);
} }
/**
* Adds each product's category and type ("Advances A/C" or "Loans A/C" and its sub type) to the list, and applies
* the optional filters {@code activeOnly}, {@code productTypeGroup} and {@code productTypeSubGroup}. With none of
* them given the list holds the same products as before, each now carrying its classification.
*/
private Collection<LoanProductData> enrichAndFilterProducts(final Collection<LoanProductData> products, final UriInfo uriInfo) {
final MultivaluedMap<String, String> query = uriInfo.getQueryParameters();
final boolean activeOnly = Boolean.parseBoolean(query.getFirst("activeOnly"));
final String group = trimToNull(query.getFirst("productTypeGroup"));
final String subGroup = trimToNull(query.getFirst("productTypeSubGroup"));
final Map<Long, Map<String, Object>> extensions = this.loanProductAdvanceExtService.retrieveAllAsMap();
final LocalDate today = DateUtils.getBusinessLocalDate();
final List<LoanProductData> result = new ArrayList<>();
for (final LoanProductData product : products) {
final Map<String, Object> ext = extensions.getOrDefault(product.getId(), Collections.emptyMap());
if (activeOnly && !isActiveOn(product, today)) {
continue;
}
if (group != null && !group.equalsIgnoreCase(String.valueOf(ext.get("productTypeGroup")))) {
continue;
}
if (subGroup != null && !subGroup.equalsIgnoreCase(String.valueOf(ext.get("productTypeSubGroup")))) {
continue;
}
product.setAdvanceExtensions(new LinkedHashMap<>(ext));
result.add(product);
}
return result;
}
/** A product is active from its start date until its close date; either end may be open. */
private static boolean isActiveOn(final LoanProductData product, final LocalDate date) {
final LocalDate start = product.getStartDate();
final LocalDate close = product.getCloseDate();
return (start == null || !start.isAfter(date)) && (close == null || !close.isBefore(date));
}
private static String trimToNull(final String value) {
return value == null || value.isBlank() ? null : value.trim();
}
@GET @GET
@Path("template") @Path("template")
@Consumes({ MediaType.APPLICATION_JSON }) @Consumes({ MediaType.APPLICATION_JSON })
@@ -61,7 +61,22 @@ public class LoanProductAdvanceExtService {
"chequeBookSizes", "chequeMaxBooksOutstanding", "chequeReissueWhenLeaves", "chequeFirstBookFree", "chequeBookSizes", "chequeMaxBooksOutstanding", "chequeReissueWhenLeaves", "chequeFirstBookFree",
"chequeFreeBooksPerYear", "chequeNamePrinted", "chequePrintDescription", "chequeStopPaymentAllowed", "chequeFreeBooksPerYear", "chequeNamePrinted", "chequePrintDescription", "chequeStopPaymentAllowed",
"chequeMaintainStockRegister", "chequeAllowWithdrawalSlip", "chequeBlockNpa", "chequeBlockLimitExpired", "chequeMaintainStockRegister", "chequeAllowWithdrawalSlip", "chequeBlockNpa", "chequeBlockLimitExpired",
"chequeBlockOverDp", "chequeBlockFrozen", "chequeBlockLienReleased" }; "chequeBlockOverDp", "chequeBlockFrozen", "chequeBlockLienReleased",
// Loans A/C (EMI) wizard fields
"securityMandatory", "guarantorsRequired", "coApplicant", "thirdPartySecurity",
"legalOpinionRequired", "propertyInsuranceRequired", "registerCharge", "releaseSecurityOnClosure",
"allowMultipleSecurities", "allowThirdPartyDeposit", "minAge", "maxAgeAtMaturity",
"minCibil", "minMonthlyIncome", "maxFoir", "maxLoansPerCustomer",
"disbursementType", "disbursementMode", "undisbursedInterest", "preEmiInterest",
"emiDueDay", "holidayRule", "moratoriumMonths", "moratoriumInterest",
"variableInstallments", "repaymentMode", "linkedAccountType", "pdcCollection",
"mandateRequired", "advanceEmi", "rateType", "registerParticular",
"applyCibilAdjustment", "rateResolvedAt", "calcPeriod", "interestStartsFrom",
"compounding", "benchmark", "resetFrequency", "resetChange",
"partPrepaymentAllowed", "minPartPayment", "allowedOn", "partPaymentsPerYear",
"partPrepaymentMode", "foreclosureFloatingIndividual", "foreclosureFixedIndividual", "foreclosureNonIndividual",
"foreclosureTakeover", "foreclosureQuoteValidDays", "autoCloseOnZeroBalance", "nocLetter",
"restructuring", "releaseCollateralOnClosure", "topUpAllowed", "balanceTransferOutAllowed" };
/** /**
* Insert or partial-update the satellite for this product. Returns the fields that actually changed so the * Insert or partial-update the satellite for this product. Returns the fields that actually changed so the
@@ -84,6 +99,18 @@ public class LoanProductAdvanceExtService {
.orElseGet(LinkedHashMap::new); .orElseGet(LinkedHashMap::new);
} }
/**
* Every product's satellite in one query, keyed by loan product id. Used by the product list so a screen can
* show and filter products by category and type without one extra call per product.
*/
public Map<Long, Map<String, Object>> retrieveAllAsMap() {
final Map<Long, Map<String, Object>> byProduct = new LinkedHashMap<>();
for (final LoanProductAdvanceExt ext : this.repository.findAll()) {
byProduct.put(ext.getLoanProductId(), toMap(ext));
}
return byProduct;
}
private boolean hasAnyAdvanceParameter(final JsonCommand command) { private boolean hasAnyAdvanceParameter(final JsonCommand command) {
for (final String param : ADVANCE_PARAMETERS) { for (final String param : ADVANCE_PARAMETERS) {
if (command.parameterExists(param)) { if (command.parameterExists(param)) {
@@ -205,6 +232,63 @@ public class LoanProductAdvanceExtService {
setBoolean(command, "chequeBlockFrozen", ext::getChequeBlockFrozen, ext::setChequeBlockFrozen, changes); setBoolean(command, "chequeBlockFrozen", ext::getChequeBlockFrozen, ext::setChequeBlockFrozen, changes);
setBoolean(command, "chequeBlockLienReleased", ext::getChequeBlockLienReleased, ext::setChequeBlockLienReleased, setBoolean(command, "chequeBlockLienReleased", ext::getChequeBlockLienReleased, ext::setChequeBlockLienReleased,
changes); changes);
// Loans A/C (EMI) wizard fields
setString(command, "securityMandatory", ext::getSecurityMandatory, ext::setSecurityMandatory, changes);
setInteger(command, "guarantorsRequired", ext::getGuarantorsRequired, ext::setGuarantorsRequired, changes);
setString(command, "coApplicant", ext::getCoApplicant, ext::setCoApplicant, changes);
setString(command, "thirdPartySecurity", ext::getThirdPartySecurity, ext::setThirdPartySecurity, changes);
setBoolean(command, "legalOpinionRequired", ext::getLegalOpinionRequired, ext::setLegalOpinionRequired, changes);
setBoolean(command, "propertyInsuranceRequired", ext::getPropertyInsuranceRequired, ext::setPropertyInsuranceRequired, changes);
setBoolean(command, "registerCharge", ext::getRegisterCharge, ext::setRegisterCharge, changes);
setBoolean(command, "releaseSecurityOnClosure", ext::getReleaseSecurityOnClosure, ext::setReleaseSecurityOnClosure, changes);
setBoolean(command, "allowMultipleSecurities", ext::getAllowMultipleSecurities, ext::setAllowMultipleSecurities, changes);
setBoolean(command, "allowThirdPartyDeposit", ext::getAllowThirdPartyDeposit, ext::setAllowThirdPartyDeposit, changes);
setInteger(command, "minAge", ext::getMinAge, ext::setMinAge, changes);
setInteger(command, "maxAgeAtMaturity", ext::getMaxAgeAtMaturity, ext::setMaxAgeAtMaturity, changes);
setInteger(command, "minCibil", ext::getMinCibil, ext::setMinCibil, changes);
setDecimal(command, "minMonthlyIncome", ext::getMinMonthlyIncome, ext::setMinMonthlyIncome, changes);
setDecimal(command, "maxFoir", ext::getMaxFoir, ext::setMaxFoir, changes);
setInteger(command, "maxLoansPerCustomer", ext::getMaxLoansPerCustomer, ext::setMaxLoansPerCustomer, changes);
setString(command, "disbursementType", ext::getDisbursementType, ext::setDisbursementType, changes);
setString(command, "disbursementMode", ext::getDisbursementMode, ext::setDisbursementMode, changes);
setString(command, "undisbursedInterest", ext::getUndisbursedInterest, ext::setUndisbursedInterest, changes);
setBoolean(command, "preEmiInterest", ext::getPreEmiInterest, ext::setPreEmiInterest, changes);
setString(command, "emiDueDay", ext::getEmiDueDay, ext::setEmiDueDay, changes);
setString(command, "holidayRule", ext::getHolidayRule, ext::setHolidayRule, changes);
setInteger(command, "moratoriumMonths", ext::getMoratoriumMonths, ext::setMoratoriumMonths, changes);
setString(command, "moratoriumInterest", ext::getMoratoriumInterest, ext::setMoratoriumInterest, changes);
setBoolean(command, "variableInstallments", ext::getVariableInstallments, ext::setVariableInstallments, changes);
setString(command, "repaymentMode", ext::getRepaymentMode, ext::setRepaymentMode, changes);
setString(command, "linkedAccountType", ext::getLinkedAccountType, ext::setLinkedAccountType, changes);
setString(command, "pdcCollection", ext::getPdcCollection, ext::setPdcCollection, changes);
setBoolean(command, "mandateRequired", ext::getMandateRequired, ext::setMandateRequired, changes);
setString(command, "advanceEmi", ext::getAdvanceEmi, ext::setAdvanceEmi, changes);
setString(command, "rateType", ext::getRateType, ext::setRateType, changes);
setString(command, "registerParticular", ext::getRegisterParticular, ext::setRegisterParticular, changes);
setBoolean(command, "applyCibilAdjustment", ext::getApplyCibilAdjustment, ext::setApplyCibilAdjustment, changes);
setString(command, "rateResolvedAt", ext::getRateResolvedAt, ext::setRateResolvedAt, changes);
setString(command, "calcPeriod", ext::getCalcPeriod, ext::setCalcPeriod, changes);
setString(command, "interestStartsFrom", ext::getInterestStartsFrom, ext::setInterestStartsFrom, changes);
setString(command, "compounding", ext::getCompounding, ext::setCompounding, changes);
setString(command, "benchmark", ext::getBenchmark, ext::setBenchmark, changes);
setString(command, "resetFrequency", ext::getResetFrequency, ext::setResetFrequency, changes);
setString(command, "resetChange", ext::getResetChange, ext::setResetChange, changes);
setBoolean(command, "partPrepaymentAllowed", ext::getPartPrepaymentAllowed, ext::setPartPrepaymentAllowed, changes);
setDecimal(command, "minPartPayment", ext::getMinPartPayment, ext::setMinPartPayment, changes);
setString(command, "allowedOn", ext::getAllowedOn, ext::setAllowedOn, changes);
setString(command, "partPaymentsPerYear", ext::getPartPaymentsPerYear, ext::setPartPaymentsPerYear, changes);
setString(command, "partPrepaymentMode", ext::getPartPrepaymentMode, ext::setPartPrepaymentMode, changes);
setDecimal(command, "foreclosureFloatingIndividual", ext::getForeclosureFloatingIndividual, ext::setForeclosureFloatingIndividual, changes);
setDecimal(command, "foreclosureFixedIndividual", ext::getForeclosureFixedIndividual, ext::setForeclosureFixedIndividual, changes);
setDecimal(command, "foreclosureNonIndividual", ext::getForeclosureNonIndividual, ext::setForeclosureNonIndividual, changes);
setDecimal(command, "foreclosureTakeover", ext::getForeclosureTakeover, ext::setForeclosureTakeover, changes);
setInteger(command, "foreclosureQuoteValidDays", ext::getForeclosureQuoteValidDays, ext::setForeclosureQuoteValidDays, changes);
setBoolean(command, "autoCloseOnZeroBalance", ext::getAutoCloseOnZeroBalance, ext::setAutoCloseOnZeroBalance, changes);
setString(command, "nocLetter", ext::getNocLetter, ext::setNocLetter, changes);
setString(command, "restructuring", ext::getRestructuring, ext::setRestructuring, changes);
setBoolean(command, "releaseCollateralOnClosure", ext::getReleaseCollateralOnClosure, ext::setReleaseCollateralOnClosure, changes);
setBoolean(command, "topUpAllowed", ext::getTopUpAllowed, ext::setTopUpAllowed, changes);
setBoolean(command, "balanceTransferOutAllowed", ext::getBalanceTransferOutAllowed, ext::setBalanceTransferOutAllowed, changes);
} }
// ---- typed setters: only write params that are present, and only report real changes ---------- // ---- typed setters: only write params that are present, and only report real changes ----------
@@ -368,6 +452,63 @@ public class LoanProductAdvanceExtService {
map.put("chequeBlockOverDp", ext.getChequeBlockOverDp()); map.put("chequeBlockOverDp", ext.getChequeBlockOverDp());
map.put("chequeBlockFrozen", ext.getChequeBlockFrozen()); map.put("chequeBlockFrozen", ext.getChequeBlockFrozen());
map.put("chequeBlockLienReleased", ext.getChequeBlockLienReleased()); map.put("chequeBlockLienReleased", ext.getChequeBlockLienReleased());
// Loans A/C (EMI) wizard fields
map.put("securityMandatory", ext.getSecurityMandatory());
map.put("guarantorsRequired", ext.getGuarantorsRequired());
map.put("coApplicant", ext.getCoApplicant());
map.put("thirdPartySecurity", ext.getThirdPartySecurity());
map.put("legalOpinionRequired", ext.getLegalOpinionRequired());
map.put("propertyInsuranceRequired", ext.getPropertyInsuranceRequired());
map.put("registerCharge", ext.getRegisterCharge());
map.put("releaseSecurityOnClosure", ext.getReleaseSecurityOnClosure());
map.put("allowMultipleSecurities", ext.getAllowMultipleSecurities());
map.put("allowThirdPartyDeposit", ext.getAllowThirdPartyDeposit());
map.put("minAge", ext.getMinAge());
map.put("maxAgeAtMaturity", ext.getMaxAgeAtMaturity());
map.put("minCibil", ext.getMinCibil());
map.put("minMonthlyIncome", ext.getMinMonthlyIncome());
map.put("maxFoir", ext.getMaxFoir());
map.put("maxLoansPerCustomer", ext.getMaxLoansPerCustomer());
map.put("disbursementType", ext.getDisbursementType());
map.put("disbursementMode", ext.getDisbursementMode());
map.put("undisbursedInterest", ext.getUndisbursedInterest());
map.put("preEmiInterest", ext.getPreEmiInterest());
map.put("emiDueDay", ext.getEmiDueDay());
map.put("holidayRule", ext.getHolidayRule());
map.put("moratoriumMonths", ext.getMoratoriumMonths());
map.put("moratoriumInterest", ext.getMoratoriumInterest());
map.put("variableInstallments", ext.getVariableInstallments());
map.put("repaymentMode", ext.getRepaymentMode());
map.put("linkedAccountType", ext.getLinkedAccountType());
map.put("pdcCollection", ext.getPdcCollection());
map.put("mandateRequired", ext.getMandateRequired());
map.put("advanceEmi", ext.getAdvanceEmi());
map.put("rateType", ext.getRateType());
map.put("registerParticular", ext.getRegisterParticular());
map.put("applyCibilAdjustment", ext.getApplyCibilAdjustment());
map.put("rateResolvedAt", ext.getRateResolvedAt());
map.put("calcPeriod", ext.getCalcPeriod());
map.put("interestStartsFrom", ext.getInterestStartsFrom());
map.put("compounding", ext.getCompounding());
map.put("benchmark", ext.getBenchmark());
map.put("resetFrequency", ext.getResetFrequency());
map.put("resetChange", ext.getResetChange());
map.put("partPrepaymentAllowed", ext.getPartPrepaymentAllowed());
map.put("minPartPayment", ext.getMinPartPayment());
map.put("allowedOn", ext.getAllowedOn());
map.put("partPaymentsPerYear", ext.getPartPaymentsPerYear());
map.put("partPrepaymentMode", ext.getPartPrepaymentMode());
map.put("foreclosureFloatingIndividual", ext.getForeclosureFloatingIndividual());
map.put("foreclosureFixedIndividual", ext.getForeclosureFixedIndividual());
map.put("foreclosureNonIndividual", ext.getForeclosureNonIndividual());
map.put("foreclosureTakeover", ext.getForeclosureTakeover());
map.put("foreclosureQuoteValidDays", ext.getForeclosureQuoteValidDays());
map.put("autoCloseOnZeroBalance", ext.getAutoCloseOnZeroBalance());
map.put("nocLetter", ext.getNocLetter());
map.put("restructuring", ext.getRestructuring());
map.put("releaseCollateralOnClosure", ext.getReleaseCollateralOnClosure());
map.put("topUpAllowed", ext.getTopUpAllowed());
map.put("balanceTransferOutAllowed", ext.getBalanceTransferOutAllowed());
return map; return map;
} }
} }
@@ -622,4 +622,5 @@
<include file="parts/0594_loan_rate_register_product_master.xml" relativeToChangelogFile="true"/> <include file="parts/0594_loan_rate_register_product_master.xml" relativeToChangelogFile="true"/>
<include file="parts/0595_seed_loan_rate_register_gl_accounts.xml" relativeToChangelogFile="true"/> <include file="parts/0595_seed_loan_rate_register_gl_accounts.xml" relativeToChangelogFile="true"/>
<include file="parts/0596_enable_delinquency_maker_checker.xml" relativeToChangelogFile="true"/> <include file="parts/0596_enable_delinquency_maker_checker.xml" relativeToChangelogFile="true"/>
<include file="parts/0597_add_loan_product_loans_ext_fields.xml" relativeToChangelogFile="true"/>
</databaseChangeLog> </databaseChangeLog>
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!-- Loans A/C (EMI) wizard fields that Fineract's loan-product schema has no home for: collateral and
guarantor rules, borrower eligibility, disbursement, repayment mode, rate source / floating reset,
part-prepayment and foreclosure. Stored on the same per-product satellite as the Advances fields.
The columns are prefixed loan_ so they never clash with an Advances column. -->
<changeSet author="cbsnexora" id="0597_add_loan_product_loans_ext_fields">
<addColumn tableName="m_product_loan_advance_ext">
<column name="loan_security_mandatory" type="VARCHAR(100)"/>
<column name="loan_guarantors_required" type="INT"/>
<column name="loan_co_applicant" type="VARCHAR(100)"/>
<column name="loan_third_party_security" type="VARCHAR(100)"/>
<column name="loan_legal_opinion_required" type="BOOLEAN"/>
<column name="loan_property_insurance_required" type="BOOLEAN"/>
<column name="loan_register_charge" type="BOOLEAN"/>
<column name="loan_release_security_on_closure" type="BOOLEAN"/>
<column name="loan_allow_multiple_securities" type="BOOLEAN"/>
<column name="loan_allow_third_party_deposit" type="BOOLEAN"/>
<column name="loan_min_age" type="INT"/>
<column name="loan_max_age_at_maturity" type="INT"/>
<column name="loan_min_cibil" type="INT"/>
<column name="loan_min_monthly_income" type="DECIMAL(19,6)"/>
<column name="loan_max_foir" type="DECIMAL(19,6)"/>
<column name="loan_max_loans_per_customer" type="INT"/>
<column name="loan_disbursement_type" type="VARCHAR(100)"/>
<column name="loan_disbursement_mode" type="VARCHAR(100)"/>
<column name="loan_undisbursed_interest" type="VARCHAR(100)"/>
<column name="loan_pre_emi_interest" type="BOOLEAN"/>
<column name="loan_emi_due_day" type="VARCHAR(100)"/>
<column name="loan_holiday_rule" type="VARCHAR(100)"/>
<column name="loan_moratorium_months" type="INT"/>
<column name="loan_moratorium_interest" type="VARCHAR(100)"/>
<column name="loan_variable_installments" type="BOOLEAN"/>
<column name="loan_repayment_mode" type="VARCHAR(100)"/>
<column name="loan_linked_account_type" type="VARCHAR(100)"/>
<column name="loan_pdc_collection" type="VARCHAR(100)"/>
<column name="loan_mandate_required" type="BOOLEAN"/>
<column name="loan_advance_emi" type="VARCHAR(100)"/>
<column name="loan_rate_type" type="VARCHAR(100)"/>
<column name="loan_register_particular" type="VARCHAR(100)"/>
<column name="loan_apply_cibil_adjustment" type="BOOLEAN"/>
<column name="loan_rate_resolved_at" type="VARCHAR(100)"/>
<column name="loan_calc_period" type="VARCHAR(100)"/>
<column name="loan_interest_starts_from" type="VARCHAR(100)"/>
<column name="loan_compounding" type="VARCHAR(100)"/>
<column name="loan_benchmark" type="VARCHAR(100)"/>
<column name="loan_reset_frequency" type="VARCHAR(100)"/>
<column name="loan_reset_change" type="VARCHAR(100)"/>
<column name="loan_part_prepayment_allowed" type="BOOLEAN"/>
<column name="loan_min_part_payment" type="DECIMAL(19,6)"/>
<column name="loan_allowed_on" type="VARCHAR(100)"/>
<column name="loan_part_payments_per_year" type="VARCHAR(100)"/>
<column name="loan_part_prepayment_mode" type="VARCHAR(100)"/>
<column name="loan_foreclosure_floating_individual" type="DECIMAL(19,6)"/>
<column name="loan_foreclosure_fixed_individual" type="DECIMAL(19,6)"/>
<column name="loan_foreclosure_non_individual" type="DECIMAL(19,6)"/>
<column name="loan_foreclosure_takeover" type="DECIMAL(19,6)"/>
<column name="loan_foreclosure_quote_valid_days" type="INT"/>
<column name="loan_auto_close_on_zero_balance" type="BOOLEAN"/>
<column name="loan_noc_letter" type="VARCHAR(100)"/>
<column name="loan_restructuring" type="VARCHAR(100)"/>
<column name="loan_release_collateral_on_closure" type="BOOLEAN"/>
<column name="loan_top_up_allowed" type="BOOLEAN"/>
<column name="loan_balance_transfer_out_allowed" type="BOOLEAN"/>
</addColumn>
</changeSet>
</databaseChangeLog>