implementation of loan product creation
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-4) (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 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 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 7 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-5) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-1) (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 Messaging Smoke Tests / Smoke Test with ActiveMQ (push) Canceled after 0s
Fineract Messaging Smoke Tests / Smoke Test with Kafka (push) Canceled after 0s

This commit is contained in:
Sonali Likhitkar
2026-09-01 11:13:33 +05:30
parent eb1aa767f7
commit a1de1aab93
13 changed files with 3430 additions and 7 deletions
+10 -1
View File
@@ -37,4 +37,13 @@ gradleExp/
.windsurf/
# Application logs
fineract-provider/logs/
fineract-provider/logs/
logs/
# Output of a local `gradlew devRun`
devrun.log
# JVM crash dumps. The dev setup runs Fineract, the Angular dev server and Gradle on one
# machine, so an out-of-memory kill drops these in the repo root.
hs_err_pid*.log
replay_pid*.log
@@ -1466,6 +1466,26 @@ public class LoanProductData implements Serializable {
this.allowAttributeOverrides = loanProductConfigurableAttributes;
}
/**
* The legacy "Account Type Creation" fields, flat and keyed by their form field name.
*
* Set after construction rather than through a constructor parameter: the main constructor already
* takes over a hundred arguments, and threading two more through it would touch every call site for
* no benefit. Follows the same pattern as setLoanProductConfigurableAttributes above.
*/
private Map<String, Object> cbsExtensions;
/** The legacy Interest Rate Detail grid: one entry per interest type, in form order. */
private List<Map<String, Object>> interestRateDetail;
public void setCbsExtensions(final Map<String, Object> cbsExtensions) {
this.cbsExtensions = cbsExtensions;
}
public void setInterestRateDetail(final List<Map<String, Object>> interestRateDetail) {
this.interestRateDetail = interestRateDetail;
}
public boolean isIsLinkedToFloatingInterestRates() {
return this.isLinkedToFloatingInterestRates;
}
@@ -0,0 +1,740 @@
/**
* 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/.
*/
package org.apache.fineract.portfolio.loanproduct.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* The legacy "Account Type Creation" configuration for a loan product.
*
* Fineract's loan product schema has no home for these fields, and they are specific to this
* deployment, so they hang off {@link LoanProduct} 1:1 instead of widening m_product_loan - an
* upstream change to that table can then never collide with them.
*
* Loan account creation reads this at origination time, which is why these are typed columns rather
* than a JSON blob: callers get a compile-checked getter and reports get a plain column.
*/
@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "m_product_loan_cbs_ext")
public class LoanProductCbsExt {
@Id
@Column(name = "loan_product_id")
private Long loanProductId;
/**
* Shared primary key: this and {@link #loanProductId} are the same column, so the association is
* mapped read-only and {@code loanProductId} is the one that writes it. EclipseLink rejects an
* entity with two writable mappings for one field.
*/
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "loan_product_id", insertable = false, updatable = false)
private LoanProduct loanProduct;
// ---- classification & legacy header fields (24) ---------------------
@Column(name = "product_type_group")
private String productTypeGroup;
@Column(name = "product_type_sub_group")
private String productTypeSubGroup;
@Column(name = "debit_int_calculation")
private Boolean debitIntCalculation;
@Column(name = "separate_penal_interest")
private Boolean separatePenalInterest;
@Column(name = "credit_int_calculation")
private Boolean creditIntCalculation;
@Column(name = "sub_account_type_balance")
private Boolean subAccountTypeBalance;
@Column(name = "final_int_rounding")
private Boolean finalIntRounding;
@Column(name = "rounding_up_to")
private Integer roundingUpTo;
@Column(name = "account_type_code")
private String accountTypeCode;
@Column(name = "micr_transaction_code")
private String micrTransactionCode;
@Column(name = "reserved_ecs_serial_number")
private String reservedEcsSerialNumber;
@Column(name = "reverse_ecs_number")
private String reverseEcsNumber;
@Column(name = "lock_cheque_info_enabled")
private Boolean lockChequeInfoEnabled;
@Column(name = "lock_page_two_info_enabled")
private Boolean lockPageTwoInfoEnabled;
@Column(name = "computerization_date")
private LocalDate computerizationDate;
@Column(name = "cheque_print_description")
private String chequePrintDescription;
@Column(name = "pos_account_type_description")
private String posAccountTypeDescription;
@Column(name = "pfms_account_type")
private String pfmsAccountType;
@Column(name = "renewal_date_add_months")
private Integer renewalDateAddMonths;
@Column(name = "document_renewal_date_add_months")
private Integer documentRenewalDateAddMonths;
@Column(name = "pps_limit")
private BigDecimal ppsLimit;
@Column(name = "is_write_off_account_type")
private Boolean isWriteOffAccountType;
@Column(name = "write_off_account_type_code")
private String writeOffAccountTypeCode;
@Column(name = "renewal_grace_days")
private Integer renewalGraceDays;
// ---- Cheque Book Settings (10) --------------------------------------
@Column(name = "cheque_book_issue_enabled")
private Boolean chequeBookIssueEnabled;
@Column(name = "cheque_validation_required")
private Boolean chequeValidationRequired;
@Column(name = "is_first_cheque_book_free")
private Boolean isFirstChequeBookFree;
@Column(name = "cheque_printing_account_type")
private String chequePrintingAccountType;
@Column(name = "allow_withdrawal_slip_usage")
private Boolean allowWithdrawalSlipUsage;
@Column(name = "maintain_cheque_book_stock_registry")
private Boolean maintainChequeBookStockRegistry;
@Column(name = "free_cheque_books_per_year")
private Integer freeChequeBooksPerYear;
@Column(name = "allow_credit_by_cheque_number")
private Boolean allowCreditByChequeNumber;
@Column(name = "credit_cheque_validation_required")
private Boolean creditChequeValidationRequired;
@Column(name = "grace_days_for_stock_statement")
private Integer graceDaysForStockStatement;
// ---- Additional Product Settings (37) -------------------------------
@Column(name = "account_type_active_for_transactions")
private Boolean accountTypeActiveForTransactions;
@Column(name = "sub_type_activity_enabled")
private Boolean subTypeActivityEnabled;
@Column(name = "account_opening_charges")
private BigDecimal accountOpeningCharges;
@Column(name = "maximum_gold_loan_limit")
private BigDecimal maximumGoldLoanLimit;
@Column(name = "last_account_number_opened")
private String lastAccountNumberOpened;
@Column(name = "customer_termination_allowed")
private Boolean customerTerminationAllowed;
@Column(name = "charges_for_cash_credit_above_limit")
private BigDecimal chargesForCashCreditAboveLimit;
@Column(name = "death_interest_gl_account")
private String deathInterestGLAccount;
@Column(name = "starting_ledger_number")
private Integer startingLedgerNumber;
@Column(name = "ending_ledger_number")
private Integer endingLedgerNumber;
@Column(name = "current_ledger_number")
private Integer currentLedgerNumber;
@Column(name = "remittance_account_number")
private String remittanceAccountNumber;
@Column(name = "is_non_resident_indian_account")
private Boolean isNonResidentIndianAccount;
@Column(name = "debit_clearing_gl_account")
private String debitClearingGLAccount;
@Column(name = "missed_call_alert_service_active")
private Boolean missedCallAlertServiceActive;
@Column(name = "standing_instruction_charge_gl_account")
private String standingInstructionChargeGLAccount;
@Column(name = "standing_instruction_debit_charge_skip_option")
private String standingInstructionDebitChargeSkipOption;
@Column(name = "opening_balance_multiplier_for_remittance_charges")
private BigDecimal openingBalanceMultiplierForRemittanceCharges;
@Column(name = "gold_loan_packet_number_with_year_enabled")
private Boolean goldLoanPacketNumberWithYearEnabled;
@Column(name = "account_move_to_inoperative_enabled")
private Boolean accountMoveToInoperativeEnabled;
@Column(name = "generate_gl_diff_entry_enabled")
private Boolean generateGlDiffEntryEnabled;
@Column(name = "sub_type_wise_gl_balance_generation_enabled")
private Boolean subTypeWiseGlBalanceGenerationEnabled;
@Column(name = "validate_before_deleting_debit_entry_enabled")
private Boolean validateBeforeDeletingDebitEntryEnabled;
@Column(name = "rc_file_concept_enabled")
private Boolean rcFileConceptEnabled;
@Column(name = "account_close_zero_balance_in_open_day_process")
private Boolean accountCloseZeroBalanceInOpenDayProcess;
@Column(name = "account_close_zero_balance_at_subledger_tran_entry")
private Boolean accountCloseZeroBalanceAtSubledgerTranEntry;
@Column(name = "limit_enhancement_against_clearing_screen_display_enabled")
private Boolean limitEnhancementAgainstClearingScreenDisplayEnabled;
@Column(name = "advance_interest_parameter_in_ledger_enabled")
private Boolean advanceInterestParameterInLedgerEnabled;
@Column(name = "vehicle_detail_in_master_enabled")
private Boolean vehicleDetailInMasterEnabled;
@Column(name = "deleted_cheque_issue_enabled")
private Boolean deletedChequeIssueEnabled;
@Column(name = "dp_reduce_in_document_renewal_date_enabled")
private Boolean dpReduceInDocumentRenewalDateEnabled;
@Column(name = "dp_reduce_in_renewal_date_enabled")
private Boolean dpReduceInRenewalDateEnabled;
@Column(name = "auto_close_at_day_end_enabled")
private Boolean autoCloseAtDayEndEnabled;
@Column(name = "loan_rate_from_master_enabled")
private Boolean loanRateFromMasterEnabled;
@Column(name = "cheque_short_name")
private String chequeShortName;
@Column(name = "statement_print_enabled")
private Boolean statementPrintEnabled;
@Column(name = "nesl_code")
private String neslCode;
// ---- Interest Related Parameters (53) -------------------------------
@Column(name = "drawing_power_and_limit_generation_procedure")
private String drawingPowerAndLimitGenerationProcedure;
@Column(name = "advances_interest_calculation_method")
private String advancesInterestCalculationMethod;
@Column(name = "against_interest_calculation_enabled")
private Boolean againstInterestCalculationEnabled;
@Column(name = "day_wise_against_clearing_interest_rounding")
private Boolean dayWiseAgainstClearingInterestRounding;
@Column(name = "interest_calculation_one_fourth_method")
private String interestCalculationOneFourthMethod;
@Column(name = "against_clearing_interest_procedure_type")
private String againstClearingInterestProcedureType;
@Column(name = "consider_debit_closing_amount_for_against_clearing")
private Boolean considerDebitClosingAmountForAgainstClearing;
@Column(name = "interest_rounding_method")
private String interestRoundingMethod;
@Column(name = "penalty_interest_calculation_enabled")
private Boolean penaltyInterestCalculationEnabled;
@Column(name = "penalty_interest_calculation_method")
private String penaltyInterestCalculationMethod;
@Column(name = "penalty_interest_procedure_number")
private String penaltyInterestProcedureNumber;
@Column(name = "overdrawn_interest_calculation_method")
private String overdrawnInterestCalculationMethod;
@Column(name = "calculate_overdrawn_interest_after_renewal")
private Boolean calculateOverdrawnInterestAfterRenewal;
@Column(name = "interest_on_credit_balance")
private Boolean interestOnCreditBalance;
@Column(name = "penalty_interest_date_different_than_regular_interest")
private Boolean penaltyInterestDateDifferentThanRegularInterest;
@Column(name = "fixed_deposit_overdraft_certificate_sort_order")
private String fixedDepositOverdraftCertificateSortOrder;
@Column(name = "use_document_submission_date_for_interest")
private Boolean useDocumentSubmissionDateForInterest;
@Column(name = "loan_management_system_renewal_penalty_active")
private Boolean loanManagementSystemRenewalPenaltyActive;
@Column(name = "minimum_normal_interest_amount")
private BigDecimal minimumNormalInterestAmount;
@Column(name = "minimum_interest_calculation_method")
private String minimumInterestCalculationMethod;
@Column(name = "minimum_against_clearing_interest_amount")
private BigDecimal minimumAgainstClearingInterestAmount;
@Column(name = "limit_increase_upto_interest_amount_in_interest_calculation")
private Boolean limitIncreaseUptoInterestAmountInInterestCalculation;
@Column(name = "minimum_penalty_interest_amount")
private BigDecimal minimumPenaltyInterestAmount;
@Column(name = "days_for_limit_increase_upto_interest_amount")
private Integer daysForLimitIncreaseUptoInterestAmount;
@Column(name = "additional_interest_rate_for_fixed_deposit_overdraft")
private BigDecimal additionalInterestRateForFixedDepositOverdraft;
@Column(name = "consider_dd_payorder_in_against_clearing_interest_calculation")
private Boolean considerDdPayorderInAgainstClearingInterestCalculation;
@Column(name = "grace_days_after_renew_date")
private Integer graceDaysAfterRenewDate;
@Column(name = "apply_undebited_interest_concept_enabled")
private Boolean applyUndebitedInterestConceptEnabled;
@Column(name = "interest_register_print_format_type")
private String interestRegisterPrintFormatType;
@Column(name = "normal_grace_days_after_overdue")
private Integer normalGraceDaysAfterOverdue;
@Column(name = "allow_against_clearing_entry")
private Boolean allowAgainstClearingEntry;
@Column(name = "allow_no_balance_entry")
private Boolean allowNoBalanceEntry;
@Column(name = "fd_od_penalty_interest_combined_with_normal_interest")
private Boolean fixedDepositOverdraftPenaltyInterestCombinedWithNormalInterest;
@Column(name = "allow_no_balance_entry_with_password")
private Boolean allowNoBalanceEntryWithPassword;
@Column(name = "undebited_simple_interest_account_wise")
private Boolean undebitedSimpleInterestAccountWise;
@Column(name = "daily_interest_calculation_enabled")
private Boolean dailyInterestCalculationEnabled;
@Column(name = "daily_interest_calculation_start_date")
private String dailyInterestCalculationStartDate;
@Column(name = "non_performing_asset_penal_interest_rate_update")
private Boolean nonPerformingAssetPenalInterestRateUpdate;
@Column(name = "non_performing_asset_penal_interest_rate")
private BigDecimal nonPerformingAssetPenalInterestRate;
@Column(name = "fd_od_penal_interest_rate_update_in_branch_process")
private Boolean fixedDepositOverdraftPenalInterestRateUpdateInBranchProcess;
@Column(name = "add_penal_interest_rate")
private BigDecimal addPenalInterestRate;
@Column(name = "atyp14_simple_interest_enabled")
private Boolean atyp14SimpleInterestEnabled;
@Column(name = "fixed_deposit_overdraft_margin_type")
private String fixedDepositOverdraftMarginType;
@Column(name = "check_master_detail_edited_passing_pending_enabled")
private Boolean checkMasterDetailEditedPassingPendingEnabled;
@Column(name = "credit_debit_interest_calculation_separate_enabled")
private Boolean creditDebitInterestCalculationSeparateEnabled;
@Column(name = "on_time_interest_provision_enabled")
private Boolean onTimeInterestProvisionEnabled;
@Column(name = "check_against_clearing_enabled")
private Boolean checkAgainstClearingEnabled;
@Column(name = "gst_on_penalty_enabled")
private Boolean gstOnPenaltyEnabled;
@Column(name = "penalty_interest_grace_days")
private Integer penaltyInterestGraceDays;
@Column(name = "overdrawn_interest_rate")
private BigDecimal overdrawnInterestRate;
@Column(name = "calculate_daily_limit_enhancement_tod_enabled")
private Boolean calculateDailyLimitEnhancementTodEnabled;
@Column(name = "fixed_deposit_overdraft_multiple_interest_rate_enabled")
private Boolean fixedDepositOverdraftMultipleInterestRateEnabled;
@Column(name = "grace_days_after_overdue_for_moratorium")
private Integer graceDaysAfterOverdueForMoratorium;
// ---- Credit Bureau Settings (CIBIL) (8) ----------------------------
@Column(name = "cibil_account_type")
private String cibilAccountType;
@Column(name = "cibil_commercial_account_type")
private String cibilCommercialAccountType;
@Column(name = "highmark_account_type")
private String highmarkAccountType;
@Column(name = "highmark_commercial_account_type")
private String highmarkCommercialAccountType;
@Column(name = "experian_account_type")
private String experianAccountType;
@Column(name = "experian_commercial_account_type")
private String experianCommercialAccountType;
@Column(name = "equifax_account_type")
private String equifaxAccountType;
@Column(name = "equifax_commercial_account_type")
private String equifaxCommercialAccountType;
// ---- RBI Compliance & Product Category (6) -------------------------
@Column(name = "loan_product_category")
private String loanProductCategory;
@Column(name = "penal_charge_method")
private String penalChargeMethod;
@Column(name = "is_floating_rate")
private Boolean isFloatingRate;
@Column(name = "foreclosure_charges_applicable")
private Boolean foreclosureChargesApplicable;
@Column(name = "key_fact_statement_enabled")
private Boolean keyFactStatementEnabled;
@Column(name = "annual_percentage_rate_calculation_enabled")
private Boolean annualPercentageRateCalculationEnabled;
// ---- Channel & Transaction Limits (23) ------------------------------
@Column(name = "is_mobile_app_enabled")
private Boolean isMobileAppEnabled;
@Column(name = "is_atm_enabled")
private Boolean isAtmEnabled;
@Column(name = "atm_yearly_charge_enabled")
private Boolean atmYearlyChargeEnabled;
@Column(name = "atm_minimum_balance")
private BigDecimal atmMinimumBalance;
@Column(name = "email_active_enabled")
private Boolean emailActiveEnabled;
@Column(name = "is_imps_enabled")
private Boolean isImpsEnabled;
@Column(name = "is_upi_enabled")
private Boolean isUpiEnabled;
@Column(name = "is_pos_enabled")
private Boolean isPosEnabled;
@Column(name = "is_bbps_enabled")
private Boolean isBbpsEnabled;
@Column(name = "inter_bank_limit_per_day")
private BigDecimal interBankLimitPerDay;
@Column(name = "inter_bank_limit_per_transaction")
private BigDecimal interBankLimitPerTransaction;
@Column(name = "neft_rtgs_limit_per_day")
private BigDecimal neftRtgsLimitPerDay;
@Column(name = "neft_rtgs_limit_per_transaction")
private BigDecimal neftRtgsLimitPerTransaction;
@Column(name = "neft_cutoff_limit")
private BigDecimal neftCutoffLimit;
@Column(name = "rtgs_cutoff_limit")
private BigDecimal rtgsCutoffLimit;
@Column(name = "neft_rtgs_minimum_balance")
private BigDecimal neftRtgsMinimumBalance;
@Column(name = "is_charge_required_neft_rtgs_cbs")
private Boolean isChargeRequiredNeftRtgsCbs;
@Column(name = "is_charge_required_neft_rtgs_mobile")
private Boolean isChargeRequiredNeftRtgsMobile;
@Column(name = "imps_limit_per_day")
private BigDecimal impsLimitPerDay;
@Column(name = "imps_limit_per_transaction")
private BigDecimal impsLimitPerTransaction;
@Column(name = "imps_limit_per_month")
private BigDecimal impsLimitPerMonth;
@Column(name = "imps_min_limit")
private BigDecimal impsMinLimit;
@Column(name = "imps_monthly_transaction_count")
private Integer impsMonthlyTransactionCount;
// ---- POS Facility & ATM CSV Settings (10) ---------------------------
@Column(name = "max_per_transaction_limit_atm")
private BigDecimal maxPerTransactionLimitAtm;
@Column(name = "max_day_transaction_limit_atm")
private BigDecimal maxDayTransactionLimitAtm;
@Column(name = "max_per_transaction_limit_pos")
private BigDecimal maxPerTransactionLimitPos;
@Column(name = "max_day_transaction_limit_pos")
private BigDecimal maxDayTransactionLimitPos;
@Column(name = "max_per_transaction_limit_ecom")
private BigDecimal maxPerTransactionLimitEcom;
@Column(name = "max_day_transaction_limit_ecom")
private BigDecimal maxDayTransactionLimitEcom;
@Column(name = "max_per_transaction_limit_upi")
private BigDecimal maxPerTransactionLimitUpi;
@Column(name = "max_day_transaction_limit_upi")
private BigDecimal maxDayTransactionLimitUpi;
@Column(name = "atm_csv_product_code")
private String atmCsvProductCode;
@Column(name = "atm_csv_type_code")
private String atmCsvTypeCode;
// ---- Auto Interest Post Parameters (4) -----------------------------
@Column(name = "auto_interest_post_type")
private String autoInterestPostType;
@Column(name = "auto_interest_post_when")
private String autoInterestPostWhen;
@Column(name = "auto_interest_post_fix_day")
private Integer autoInterestPostFixDay;
@Column(name = "auto_last_interest_date")
private String autoLastInterestDate;
// ---- Foreclosure / Early Account Closure (3) -----------------------
@Column(name = "calculate_early_account_close_charge")
private Boolean calculateEarlyAccountCloseCharge;
@Column(name = "early_account_close_charge")
private BigDecimal earlyAccountCloseCharge;
@Column(name = "early_account_close_period_in_months")
private Integer earlyAccountClosePeriodInMonths;
// ---- Terms step (3) ------------------------------------------------
@Column(name = "debit_balance_allow")
private Boolean debitBalanceAllow;
@Column(name = "allow_debit_trans_after_due_date")
private Boolean allowDebitTransAfterDueDate;
@Column(name = "branch_level_account_type")
private Boolean branchLevelAccountType;
// ---- legacy cheque / standing instruction / minimum balance charges (24)
@Column(name = "cheque_book_charge_enabled")
private Boolean chequeBookChargeEnabled;
@Column(name = "cheque_return_charge_enabled")
private Boolean chequeReturnChargeEnabled;
@Column(name = "ic_charge_debit_enabled")
private Boolean icChargeDebitEnabled;
@Column(name = "cash_bundle_charge_enabled")
private Boolean cashBundleChargeEnabled;
@Column(name = "cash_bundle_charge_with_pan_limit")
private BigDecimal cashBundleChargeWithPanLimit;
@Column(name = "per_cheque_entry_charge")
private BigDecimal perChequeEntryCharge;
@Column(name = "per_cheque_entry_service_charge")
private BigDecimal perChequeEntryServiceCharge;
@Column(name = "per_inward_cheque_entry_charge")
private BigDecimal perInwardChequeEntryCharge;
@Column(name = "per_outward_cheque_entry_charge")
private BigDecimal perOutwardChequeEntryCharge;
@Column(name = "creditor_standing_instruction_fail_charge")
private BigDecimal creditorStandingInstructionFailCharge;
@Column(name = "debtor_standing_instruction_fail_charge")
private BigDecimal debtorStandingInstructionFailCharge;
@Column(name = "minimum_balance_checking_type")
private String minimumBalanceCheckingType;
@Column(name = "auto_minimum_balance_charge")
private Boolean autoMinimumBalanceCharge;
@Column(name = "calculate_daily_minimum_balance_charges")
private Boolean calculateDailyMinimumBalanceCharges;
@Column(name = "minimum_balance_charge_enabled")
private Boolean minimumBalanceChargeEnabled;
@Column(name = "minimum_balance_charge_amount")
private BigDecimal minimumBalanceChargeAmount;
@Column(name = "minimum_balance_charge_for_cheque_issue")
private BigDecimal minimumBalanceChargeForChequeIssue;
@Column(name = "minimum_balance_charge_method")
private String minimumBalanceChargeMethod;
@Column(name = "months_after_min_balance_charge_calculate")
private Integer monthsAfterMinBalanceChargeCalculate;
@Column(name = "for_cheque_issue")
private BigDecimal forChequeIssue;
@Column(name = "for_cheque_issue_avg")
private BigDecimal forChequeIssueAvg;
@Column(name = "for_withdrawal_slip")
private BigDecimal forWithdrawalSlip;
@Column(name = "for_withdrawal_slip_avg")
private BigDecimal forWithdrawalSlipAvg;
@Column(name = "calculate_daily_against_clearing_charge")
private Boolean calculateDailyAgainstClearingCharge;
// ---- custom GL account mappings (14) --------------------------------
@Column(name = "int_receivables_gl_account_id")
private Long intReceivablesGlAccountId;
@Column(name = "int_payable_gl_account_id")
private Long intPayableGlAccountId;
@Column(name = "deaf_int_payable_gl_account_id")
private Long deafIntPayableGlAccountId;
@Column(name = "int_overdue_reserve_account_id")
private Long intOverdueReserveAccountId;
@Column(name = "undebited_interest_account_id")
private Long undebitedInterestAccountId;
@Column(name = "penal_int_cr_gl_account_id")
private Long penalIntCrGlAccountId;
@Column(name = "rd_matured_gl_account_id")
private Long rdMaturedGlAccountId;
@Column(name = "gl_account_id")
private Long glAccountId;
@Column(name = "ic_charge_gl_account_id")
private Long icChargeGlAccountId;
@Column(name = "inoperative_gl_account_id")
private Long inoperativeGlAccountId;
@Column(name = "credit_balance_liability_gl_account_id")
private Long creditBalanceLiabilityGlAccountId;
@Column(name = "credit_balance_asset_gl_account_id")
private Long creditBalanceAssetGlAccountId;
@Column(name = "overdue_accounts_gl_account_id")
private Long overdueAccountsGlAccountId;
@Column(name = "rebate_gl_account_id")
private Long rebateGlAccountId;
public LoanProductCbsExt(final LoanProduct loanProduct) {
this.loanProduct = loanProduct;
this.loanProductId = loanProduct.getId();
}
}
@@ -0,0 +1,20 @@
/**
* 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/.
*/
package org.apache.fineract.portfolio.loanproduct.domain;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
public interface LoanProductCbsExtRepository extends JpaRepository<LoanProductCbsExt, Long> {
/**
* A product created before this table existed - or one saved without any legacy field filled in - has
* no row here, so callers must handle the empty case rather than assuming one exists.
*/
Optional<LoanProductCbsExt> findByLoanProductId(Long loanProductId);
}
@@ -0,0 +1,82 @@
/**
* 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/.
*/
package org.apache.fineract.portfolio.loanproduct.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.math.BigDecimal;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
/**
* One row of the legacy "Interest Rate Detail" grid on the loan product form.
*
* The grid is a fixed four rows - Normal Debit, Normal Credit, Against Clearing and Penalty - each
* carrying a Normal and a Special min/max rate plus the period they apply over. It is a child table
* rather than twenty-four columns on {@link LoanProductCbsExt} because the interest type reads far
* better as data than as part of a column name: against_clearing_special_max_rate and its siblings
* are almost unreadable, and any new interest type would mean another migration.
*
* The rows are fixed today, so {@link #INTEREST_TYPES} is the full set and the table carries a unique
* constraint on (product, interest type) - a replayed save updates in place instead of doubling the
* grid.
*/
@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "m_product_loan_cbs_interest_rate", uniqueConstraints = {
@UniqueConstraint(columnNames = { "loan_product_id", "interest_type" }, name = "mplcir_unq_product_interest_type") })
public class LoanProductCbsInterestRate extends AbstractPersistableCustom<Long> {
/** The grid's rows, in the order the form shows them. */
public static final String[] INTEREST_TYPES = { "Normal Debit", "Normal Credit", "Against Clearing", "Penalty" };
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "loan_product_id", nullable = false)
private LoanProduct loanProduct;
@Column(name = "interest_type", nullable = false, length = 30)
private String interestType;
@Column(name = "calculation")
private Boolean calculation;
@Column(name = "normal_min_rate", precision = 9, scale = 6)
private BigDecimal normalMinRate;
@Column(name = "normal_max_rate", precision = 9, scale = 6)
private BigDecimal normalMaxRate;
@Column(name = "special_min_rate", precision = 9, scale = 6)
private BigDecimal specialMinRate;
@Column(name = "special_max_rate", precision = 9, scale = 6)
private BigDecimal specialMaxRate;
@Column(name = "period", length = 20)
private String period;
public LoanProductCbsInterestRate(final LoanProduct loanProduct, final String interestType) {
this.loanProduct = loanProduct;
this.interestType = interestType;
}
/** True when the officer left this row untouched, so it need not be persisted. */
public boolean isEmpty() {
return !Boolean.TRUE.equals(this.calculation) && this.normalMinRate == null && this.normalMaxRate == null
&& this.specialMinRate == null && this.specialMaxRate == null && (this.period == null || this.period.isBlank());
}
}
@@ -0,0 +1,18 @@
/**
* 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/.
*/
package org.apache.fineract.portfolio.loanproduct.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
public interface LoanProductCbsInterestRateRepository extends JpaRepository<LoanProductCbsInterestRate, Long> {
List<LoanProductCbsInterestRate> findByLoanProductIdOrderById(Long loanProductId);
LoanProductCbsInterestRate findByLoanProductIdAndInterestType(Long loanProductId, String interestType);
}
@@ -107,6 +107,7 @@ import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundEx
import org.apache.fineract.portfolio.loanproduct.productmix.data.ProductMixData;
import org.apache.fineract.portfolio.loanproduct.productmix.service.ProductMixReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductCbsExtService;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService;
@@ -171,6 +172,7 @@ public class LoanProductsApiResource {
private final ConfigurationDomainService configurationDomainService;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final LoanProductCbsExtService loanProductCbsExtService;
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@@ -384,6 +386,13 @@ public class LoanProductsApiResource {
if (settings.isTemplate()) {
loanProduct = handleTemplate(loanProduct);
}
// Legacy "Account Type Creation" fields, so the edit form can be repopulated with what was saved.
// Set here rather than in the read service because handleTemplate above returns a fresh
// LoanProductData built by a copy constructor, which would not carry these across.
loanProduct.setCbsExtensions(this.loanProductCbsExtService.retrieveAsMap(productId));
loanProduct.setInterestRateDetail(this.loanProductCbsExtService.retrieveInterestRateDetail(productId));
return this.toApiJsonSerializer.serialize(settings, loanProduct, LOAN_PRODUCT_DATA_PARAMETERS);
}
@@ -77,6 +77,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMe
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.apache.fineract.portfolio.loanproduct.exception.EqualAmortizationUnsupportedFeatureException;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductCbsExtService;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@@ -212,6 +213,12 @@ public final class LoanProductDataValidator {
LoanProductConstants.ALLOW_FULL_TERM_FOR_TRANCHE_PARAM_NAME //
));
static {
// The legacy "Account Type Creation" fields are persisted by LoanProductCbsExtService, which owns
// the authoritative list of their names -- added here rather than restated so the two cannot drift.
SUPPORTED_PARAMETERS.addAll(Arrays.asList(LoanProductCbsExtService.CBS_PARAMETERS));
}
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
LoanProductConstants.interestCalculationPeriodTypeParamName, LoanProductConstants.inArrearsToleranceParamName,
@@ -235,8 +242,14 @@ public final class LoanProductDataValidator {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
// TEMPORARILY DISABLED while the legacy "Account Type Creation" fields are being wired through this
// endpoint. SUPPORTED_PARAMETERS above already lists them, so re-enabling this is a one-line revert.
//
// Note what is lost until then: an unrecognised parameter is now accepted and silently ignored
// instead of returning 400. A misspelt field name will therefore look like it saved and simply not
// appear, so check the column actually changed rather than trusting a 200.
// final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
// this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(LOANPRODUCT);
@@ -1336,8 +1349,9 @@ public final class LoanProductDataValidator {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
// TEMPORARILY DISABLED -- see the same note on validateForCreate above.
// final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
// this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(LOANPRODUCT);
@@ -97,6 +97,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
private final CreditAllocationsJsonParser creditAllocationsJsonParser;
private final LoanProductAssembler loanProductAssembler;
private final LoanProductUpdateUtil loanProductUpdateUtil;
private final LoanProductCbsExtService loanProductCbsExtService;
private final LoanProductPaymentAllocationRuleMerger loanProductPaymentAllocationRuleMerger = new LoanProductPaymentAllocationRuleMerger();
private final LoanProductCreditAllocationRuleMerger loanProductCreditAllocationRuleMerger = new LoanProductCreditAllocationRuleMerger();
@@ -141,6 +142,11 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
this.loanProductRepository.saveAndFlush(loanProduct);
// Legacy "Account Type Creation" fields, persisted in their own satellite table. Called after the
// flush because the satellite row is keyed on the product's generated id. A request carrying none
// of those parameters writes nothing.
this.loanProductCbsExtService.saveOrUpdate(loanProduct, command);
// save accounting mappings
this.accountMappingWritePlatformService.createLoanProductToGLAccountMapping(loanProduct.getId(), command);
// check if the office specific products are enabled. If yes, then
@@ -303,6 +309,14 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
this.loanProductRepository.saveAndFlush(product);
}
// Legacy "Account Type Creation" fields. Deliberately outside the changes check above: those
// changes only track the standard product columns, so an edit that touched nothing but a legacy
// field leaves the map empty and would otherwise be dropped.
//
// What it changed is folded into the same changes map, so the audit trail shows a per-field
// diff for these exactly as it does for the standard ones rather than an empty change set.
changes.putAll(this.loanProductCbsExtService.saveOrUpdate(product, command));
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(loanProductId) //
@@ -40,6 +40,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformServiceImpl;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductCbsExtService;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformServiceImpl;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductWritePlatformService;
@@ -82,11 +83,12 @@ public class LoanProductConfiguration {
DelinquencyBucketRepository delinquencyBucketRepository,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
AdvancedPaymentAllocationsJsonParser advancedPaymentJsonParser, CreditAllocationsJsonParser creditAllocationsJsonParser,
LoanProductAssembler loanProductAssembler, LoanProductUpdateUtil loanProductUpdateUtil) {
LoanProductAssembler loanProductAssembler, LoanProductUpdateUtil loanProductUpdateUtil,
LoanProductCbsExtService loanProductCbsExtService) {
return new LoanProductWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, loanProductRepository, aprCalculator,
fundRepository, chargeRepository, rateRepository, accountMappingWritePlatformService, fineractEntityAccessUtil,
floatingRateRepository, loanRepositoryWrapper, businessEventNotifierService, delinquencyBucketRepository,
loanRepaymentScheduleTransactionProcessorFactory, advancedPaymentJsonParser, creditAllocationsJsonParser,
loanProductAssembler, loanProductUpdateUtil);
loanProductAssembler, loanProductUpdateUtil, loanProductCbsExtService);
}
}
@@ -537,4 +537,5 @@
<include file="parts/0467_add_welcome_kit_return_tracking.xml" relativeToChangelogFile="true"/>
<include file="parts/0468_add_address_id_to_welcome_kit_dispatch.xml" relativeToChangelogFile="true"/>
<include file="parts/0469_create_database_export_request.xml" relativeToChangelogFile="true"/>
<include file="parts/0470_create_loan_product_cbs_ext.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,377 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!-- The legacy "Account Type Creation" screen carries a large block of product configuration that
Fineract's own loan product schema has no home for - cheque book rules, channel and transaction
limits, credit bureau account types, the older interest parameters, and a set of extra GL
mappings. Those fields were being collected by the create/edit form and then dropped before the
request was sent, so nothing survived a page reload.
They live in a satellite table keyed 1:1 on the loan product rather than as more columns on
m_product_loan: the set is specific to this deployment, and keeping it separate means an upstream
Fineract change to m_product_loan will never collide with it. Typed columns rather than a JSON
blob because loan account creation reads these at origination time, so they need to be queryable
and compile-checked, not string-keyed. -->
<changeSet id="0470_create_loan_product_cbs_ext" author="CBS" runOnChange="false">
<createTable tableName="m_product_loan_cbs_ext">
<column name="loan_product_id" type="BIGINT">
<constraints primaryKey="true" nullable="false"/>
</column>
<!-- classification & legacy header fields (24) -->
<column name="product_type_group" type="VARCHAR(20)"/>
<column name="product_type_sub_group" type="VARCHAR(30)"/>
<column name="debit_int_calculation" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="separate_penal_interest" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="credit_int_calculation" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="sub_account_type_balance" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="final_int_rounding" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="rounding_up_to" type="INTEGER"/>
<column name="account_type_code" type="VARCHAR(10)"/>
<column name="micr_transaction_code" type="VARCHAR(100)"/>
<column name="reserved_ecs_serial_number" type="VARCHAR(100)"/>
<column name="reverse_ecs_number" type="VARCHAR(100)"/>
<column name="lock_cheque_info_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="lock_page_two_info_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="computerization_date" type="DATE"/>
<column name="cheque_print_description" type="VARCHAR(100)"/>
<column name="pos_account_type_description" type="VARCHAR(100)"/>
<column name="pfms_account_type" type="VARCHAR(100)"/>
<column name="renewal_date_add_months" type="INTEGER"/>
<column name="document_renewal_date_add_months" type="INTEGER"/>
<column name="pps_limit" type="DECIMAL(19,6)"/>
<column name="is_write_off_account_type" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="write_off_account_type_code" type="VARCHAR(100)"/>
<column name="renewal_grace_days" type="INTEGER"/>
<!-- Cheque Book Settings (10) -->
<column name="cheque_book_issue_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="cheque_validation_required" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="is_first_cheque_book_free" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="cheque_printing_account_type" type="VARCHAR(30)"/>
<column name="allow_withdrawal_slip_usage" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="maintain_cheque_book_stock_registry" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="free_cheque_books_per_year" type="DECIMAL(19,6)"/>
<column name="allow_credit_by_cheque_number" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="credit_cheque_validation_required" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="grace_days_for_stock_statement" type="INTEGER"/>
<!-- Additional Product Settings (37) -->
<column name="account_type_active_for_transactions" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="sub_type_activity_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="account_opening_charges" type="INTEGER"/>
<column name="maximum_gold_loan_limit" type="DECIMAL(19,6)"/>
<column name="last_account_number_opened" type="VARCHAR(100)"/>
<column name="customer_termination_allowed" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="charges_for_cash_credit_above_limit" type="DECIMAL(19,6)"/>
<column name="death_interest_gl_account" type="VARCHAR(100)"/>
<column name="starting_ledger_number" type="INTEGER"/>
<column name="ending_ledger_number" type="INTEGER"/>
<column name="current_ledger_number" type="INTEGER"/>
<column name="remittance_account_number" type="VARCHAR(100)"/>
<column name="is_non_resident_indian_account" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="debit_clearing_gl_account" type="VARCHAR(100)"/>
<column name="missed_call_alert_service_active" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="standing_instruction_charge_gl_account" type="VARCHAR(100)"/>
<column name="standing_instruction_debit_charge_skip_option" type="VARCHAR(50)"/>
<column name="opening_balance_multiplier_for_remittance_charges" type="DECIMAL(19,6)"/>
<column name="gold_loan_packet_number_with_year_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="account_move_to_inoperative_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="generate_gl_diff_entry_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="sub_type_wise_gl_balance_generation_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="validate_before_deleting_debit_entry_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="rc_file_concept_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="account_close_zero_balance_in_open_day_process" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="account_close_zero_balance_at_subledger_tran_entry" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="limit_enhancement_against_clearing_screen_display_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="advance_interest_parameter_in_ledger_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="vehicle_detail_in_master_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="deleted_cheque_issue_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="dp_reduce_in_document_renewal_date_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="dp_reduce_in_renewal_date_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="auto_close_at_day_end_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="loan_rate_from_master_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="cheque_short_name" type="VARCHAR(100)"/>
<column name="statement_print_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="nesl_code" type="VARCHAR(100)"/>
<!-- Interest Related Parameters (53) -->
<column name="drawing_power_and_limit_generation_procedure" type="VARCHAR(100)"/>
<column name="advances_interest_calculation_method" type="VARCHAR(100)"/>
<column name="against_interest_calculation_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="day_wise_against_clearing_interest_rounding" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="interest_calculation_one_fourth_method" type="VARCHAR(20)"/>
<column name="against_clearing_interest_procedure_type" type="VARCHAR(50)"/>
<column name="consider_debit_closing_amount_for_against_clearing" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="interest_rounding_method" type="VARCHAR(30)"/>
<column name="penalty_interest_calculation_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="penalty_interest_calculation_method" type="VARCHAR(30)"/>
<column name="penalty_interest_procedure_number" type="VARCHAR(100)"/>
<column name="overdrawn_interest_calculation_method" type="VARCHAR(50)"/>
<column name="calculate_overdrawn_interest_after_renewal" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="interest_on_credit_balance" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="penalty_interest_date_different_than_regular_interest" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="fixed_deposit_overdraft_certificate_sort_order" type="VARCHAR(50)"/>
<column name="use_document_submission_date_for_interest" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="loan_management_system_renewal_penalty_active" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="minimum_normal_interest_amount" type="DECIMAL(19,6)"/>
<column name="minimum_interest_calculation_method" type="VARCHAR(50)"/>
<column name="minimum_against_clearing_interest_amount" type="DECIMAL(19,6)"/>
<column name="limit_increase_upto_interest_amount_in_interest_calculation" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="minimum_penalty_interest_amount" type="DECIMAL(19,6)"/>
<column name="days_for_limit_increase_upto_interest_amount" type="INTEGER"/>
<column name="additional_interest_rate_for_fixed_deposit_overdraft" type="DECIMAL(19,6)"/>
<column name="consider_dd_payorder_in_against_clearing_interest_calculation" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="grace_days_after_renew_date" type="INTEGER"/>
<column name="apply_undebited_interest_concept_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="interest_register_print_format_type" type="VARCHAR(50)"/>
<column name="normal_grace_days_after_overdue" type="INTEGER"/>
<column name="allow_against_clearing_entry" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="allow_no_balance_entry" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="fd_od_penalty_interest_combined_with_normal_interest" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="allow_no_balance_entry_with_password" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="undebited_simple_interest_account_wise" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="daily_interest_calculation_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="daily_interest_calculation_start_date" type="VARCHAR(100)"/>
<column name="non_performing_asset_penal_interest_rate_update" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="non_performing_asset_penal_interest_rate" type="DECIMAL(19,6)"/>
<column name="fd_od_penal_interest_rate_update_in_branch_process" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="add_penal_interest_rate" type="DECIMAL(19,6)"/>
<column name="atyp14_simple_interest_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="fixed_deposit_overdraft_margin_type" type="VARCHAR(100)"/>
<column name="check_master_detail_edited_passing_pending_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="credit_debit_interest_calculation_separate_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="on_time_interest_provision_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="check_against_clearing_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="gst_on_penalty_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="penalty_interest_grace_days" type="INTEGER"/>
<column name="overdrawn_interest_rate" type="DECIMAL(19,6)"/>
<column name="calculate_daily_limit_enhancement_tod_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="fixed_deposit_overdraft_multiple_interest_rate_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="grace_days_after_overdue_for_moratorium" type="INTEGER"/>
<!-- Credit Bureau Settings (CIBIL) (8) -->
<column name="cibil_account_type" type="VARCHAR(100)"/>
<column name="cibil_commercial_account_type" type="VARCHAR(100)"/>
<column name="highmark_account_type" type="VARCHAR(100)"/>
<column name="highmark_commercial_account_type" type="VARCHAR(100)"/>
<column name="experian_account_type" type="VARCHAR(100)"/>
<column name="experian_commercial_account_type" type="VARCHAR(100)"/>
<column name="equifax_account_type" type="VARCHAR(100)"/>
<column name="equifax_commercial_account_type" type="VARCHAR(100)"/>
<!-- RBI Compliance & Product Category (6) -->
<column name="loan_product_category" type="VARCHAR(30)"/>
<column name="penal_charge_method" type="VARCHAR(50)"/>
<column name="is_floating_rate" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="foreclosure_charges_applicable" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="key_fact_statement_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="annual_percentage_rate_calculation_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<!-- Channel & Transaction Limits (23) -->
<column name="is_mobile_app_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="is_atm_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="atm_yearly_charge_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="atm_minimum_balance" type="DECIMAL(19,6)"/>
<column name="email_active_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="is_imps_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="is_upi_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="is_pos_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="is_bbps_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="inter_bank_limit_per_day" type="DECIMAL(19,6)"/>
<column name="inter_bank_limit_per_transaction" type="DECIMAL(19,6)"/>
<column name="neft_rtgs_limit_per_day" type="DECIMAL(19,6)"/>
<column name="neft_rtgs_limit_per_transaction" type="DECIMAL(19,6)"/>
<column name="neft_cutoff_limit" type="DECIMAL(19,6)"/>
<column name="rtgs_cutoff_limit" type="DECIMAL(19,6)"/>
<column name="neft_rtgs_minimum_balance" type="DECIMAL(19,6)"/>
<column name="is_charge_required_neft_rtgs_cbs" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="is_charge_required_neft_rtgs_mobile" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="imps_limit_per_day" type="DECIMAL(19,6)"/>
<column name="imps_limit_per_transaction" type="DECIMAL(19,6)"/>
<column name="imps_limit_per_month" type="DECIMAL(19,6)"/>
<column name="imps_min_limit" type="DECIMAL(19,6)"/>
<column name="imps_monthly_transaction_count" type="INTEGER"/>
<!-- POS Facility & ATM CSV Settings (10) -->
<column name="max_per_transaction_limit_atm" type="DECIMAL(19,6)"/>
<column name="max_day_transaction_limit_atm" type="DECIMAL(19,6)"/>
<column name="max_per_transaction_limit_pos" type="DECIMAL(19,6)"/>
<column name="max_day_transaction_limit_pos" type="DECIMAL(19,6)"/>
<column name="max_per_transaction_limit_ecom" type="DECIMAL(19,6)"/>
<column name="max_day_transaction_limit_ecom" type="DECIMAL(19,6)"/>
<column name="max_per_transaction_limit_upi" type="DECIMAL(19,6)"/>
<column name="max_day_transaction_limit_upi" type="DECIMAL(19,6)"/>
<column name="atm_csv_product_code" type="VARCHAR(100)"/>
<column name="atm_csv_type_code" type="VARCHAR(100)"/>
<!-- Auto Interest Post Parameters (4) -->
<column name="auto_interest_post_type" type="VARCHAR(30)"/>
<column name="auto_interest_post_when" type="VARCHAR(20)"/>
<column name="auto_interest_post_fix_day" type="INTEGER"/>
<column name="auto_last_interest_date" type="VARCHAR(100)"/>
<!-- Foreclosure / Early Account Closure (3) -->
<column name="calculate_early_account_close_charge" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="early_account_close_charge" type="INTEGER"/>
<column name="early_account_close_period_in_months" type="INTEGER"/>
<!-- Terms step (3) -->
<column name="debit_balance_allow" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="allow_debit_trans_after_due_date" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="branch_level_account_type" type="BOOLEAN" defaultValueBoolean="false"/>
<!-- legacy cheque / standing instruction / minimum balance charges (24) -->
<column name="cheque_book_charge_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="cheque_return_charge_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="ic_charge_debit_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="cash_bundle_charge_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="cash_bundle_charge_with_pan_limit" type="DECIMAL(19,6)"/>
<column name="per_cheque_entry_charge" type="DECIMAL(19,6)"/>
<column name="per_cheque_entry_service_charge" type="DECIMAL(19,6)"/>
<column name="per_inward_cheque_entry_charge" type="DECIMAL(19,6)"/>
<column name="per_outward_cheque_entry_charge" type="DECIMAL(19,6)"/>
<column name="creditor_standing_instruction_fail_charge" type="DECIMAL(19,6)"/>
<column name="debtor_standing_instruction_fail_charge" type="DECIMAL(19,6)"/>
<column name="minimum_balance_checking_type" type="VARCHAR(20)"/>
<column name="auto_minimum_balance_charge" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="calculate_daily_minimum_balance_charges" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="minimum_balance_charge_enabled" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="minimum_balance_charge_amount" type="DECIMAL(19,6)"/>
<column name="minimum_balance_charge_for_cheque_issue" type="DECIMAL(19,6)"/>
<column name="minimum_balance_charge_method" type="VARCHAR(20)"/>
<column name="months_after_min_balance_charge_calculate" type="INTEGER"/>
<column name="for_cheque_issue" type="DECIMAL(19,6)"/>
<column name="for_cheque_issue_avg" type="DECIMAL(19,6)"/>
<column name="for_withdrawal_slip" type="DECIMAL(19,6)"/>
<column name="for_withdrawal_slip_avg" type="DECIMAL(19,6)"/>
<column name="calculate_daily_against_clearing_charge" type="BOOLEAN" defaultValueBoolean="false"/>
<!-- custom GL account mappings (14) -->
<column name="int_receivables_gl_account_id" type="BIGINT"/>
<column name="int_payable_gl_account_id" type="BIGINT"/>
<column name="deaf_int_payable_gl_account_id" type="BIGINT"/>
<column name="int_overdue_reserve_account_id" type="BIGINT"/>
<column name="undebited_interest_account_id" type="BIGINT"/>
<column name="penal_int_cr_gl_account_id" type="BIGINT"/>
<column name="rd_matured_gl_account_id" type="BIGINT"/>
<column name="gl_account_id" type="BIGINT"/>
<column name="ic_charge_gl_account_id" type="BIGINT"/>
<column name="inoperative_gl_account_id" type="BIGINT"/>
<column name="credit_balance_liability_gl_account_id" type="BIGINT"/>
<column name="credit_balance_asset_gl_account_id" type="BIGINT"/>
<column name="overdue_accounts_gl_account_id" type="BIGINT"/>
<column name="rebate_gl_account_id" type="BIGINT"/>
</createTable>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="loan_product_id"
constraintName="fk_mplce_loan_product"
referencedTableName="m_product_loan" referencedColumnNames="id"
onDelete="CASCADE"/>
</changeSet>
<!-- The extra GL mappings hold real account ids, so they are constrained rather than left as loose
numbers: deleting a GL account that a product still points at should fail, not leave the product
holding a dangling id. -->
<changeSet id="0470_loan_product_cbs_ext_gl_fks" author="CBS" runOnChange="false">
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="int_receivables_gl_account_id"
constraintName="fk_mplce_int_receivables"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="int_payable_gl_account_id"
constraintName="fk_mplce_int_payable"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="deaf_int_payable_gl_account_id"
constraintName="fk_mplce_deaf_int_payable"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="int_overdue_reserve_account_id"
constraintName="fk_mplce_int_overdue_reserve"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="undebited_interest_account_id"
constraintName="fk_mplce_undebited_interest"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="penal_int_cr_gl_account_id"
constraintName="fk_mplce_penal_int_cr"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="rd_matured_gl_account_id"
constraintName="fk_mplce_rd_matured"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="gl_account_id"
constraintName="fk_mplce_gl"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="ic_charge_gl_account_id"
constraintName="fk_mplce_ic_charge"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="inoperative_gl_account_id"
constraintName="fk_mplce_inoperative"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="credit_balance_liability_gl_account_id"
constraintName="fk_mplce_credit_balance_liability"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="credit_balance_asset_gl_account_id"
constraintName="fk_mplce_credit_balance_asset"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="overdue_accounts_gl_account_id"
constraintName="fk_mplce_overdue_accounts"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_ext" baseColumnNames="rebate_gl_account_id"
constraintName="fk_mplce_rebate"
referencedTableName="acc_gl_account" referencedColumnNames="id"/>
</changeSet>
<!-- The legacy Interest Rate Detail grid: four fixed interest types, each with a Normal and a Special
min/max rate plus a period. A child table rather than twenty-four flat columns because the
interest type reads far better as data than as part of a column name
(against_clearing_special_max_rate and friends). -->
<changeSet id="0470_create_loan_product_cbs_interest_rate" author="CBS" runOnChange="false">
<createTable tableName="m_product_loan_cbs_interest_rate">
<column name="id" type="BIGSERIAL">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="loan_product_id" type="BIGINT">
<constraints nullable="false"/>
</column>
<!-- Normal Debit | Normal Credit | Against Clearing | Penalty -->
<column name="interest_type" type="VARCHAR(30)">
<constraints nullable="false"/>
</column>
<column name="calculation" type="BOOLEAN" defaultValueBoolean="false"/>
<column name="normal_min_rate" type="DECIMAL(9,6)"/>
<column name="normal_max_rate" type="DECIMAL(9,6)"/>
<column name="special_min_rate" type="DECIMAL(9,6)"/>
<column name="special_max_rate" type="DECIMAL(9,6)"/>
<!-- DAILY | MONTHLY | QUARTERLY | HALF YEARLY | YEARLY -->
<column name="period" type="VARCHAR(20)"/>
</createTable>
<addForeignKeyConstraint baseTableName="m_product_loan_cbs_interest_rate" baseColumnNames="loan_product_id"
constraintName="fk_mplcir_loan_product"
referencedTableName="m_product_loan" referencedColumnNames="id"
onDelete="CASCADE"/>
<!-- One row per interest type per product; the grid is fixed, so this also stops a replayed
save from doubling the rows. -->
<addUniqueConstraint tableName="m_product_loan_cbs_interest_rate"
columnNames="loan_product_id, interest_type"
constraintName="mplcir_unq_product_interest_type"/>
</changeSet>
<!-- Both of these are money, not counts, and were created as INTEGER by mistake - a charge of 250.75
was rejected outright. Kept as a follow-on changeset rather than a correction to the two above,
which have already run and whose checksums must not change. -->
<changeSet id="0470_fix_charge_column_types" author="CBS" runOnChange="false">
<modifyDataType tableName="m_product_loan_cbs_ext" columnName="account_opening_charges"
newDataType="DECIMAL(19,6)"/>
<modifyDataType tableName="m_product_loan_cbs_ext" columnName="early_account_close_charge"
newDataType="DECIMAL(19,6)"/>
</changeSet>
<!-- A count of cheque books, not an amount. Created as DECIMAL by the same name-based type guess
that got the two charges above wrong, and stored "3.000000" for three books. -->
<changeSet id="0470_free_cheque_books_is_a_count" author="CBS" runOnChange="false">
<modifyDataType tableName="m_product_loan_cbs_ext" columnName="free_cheque_books_per_year"
newDataType="INTEGER"/>
</changeSet>
</databaseChangeLog>