Merge latest feature/advanceLoanProduct into feature/merge-dds-advance-loan-into-dev-latest
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 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 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 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 Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / 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-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 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
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (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 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 8 of 10) (push) Canceled after 0s
Fineract E2E Tests / E2E Tests (Shard 9 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 Cargo & Unit- & Integration tests - MariaDB / test (test-core-1) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MariaDB / test (test-core-4) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / test (test-core-3) (push) Canceled after 0s
Fineract Cargo & Unit- & Integration tests - MySQL / 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-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 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
Fineract Cargo & Unit- & Integration tests - PostgreSQL / test (test-core-5) (push) Canceled after 0s
Brings Sonali's four new commits (7-12 Oct): - Loans A/C extension fields (collateral, eligibility, repayment, rate source, prepayment) - Maker-checker for Delinquency Ranges and Buckets - Loan interest rate register product master and GL account seeding - Her merge of dev-latest (7 Oct) Conflict resolved: - changelog-tenant.xml: kept both. 0594_loan_rate_register_product_ master, 0595_seed_loan_rate_register_gl_accounts, 0596_enable_delinquency_maker_checker and 0597_add_loan_product_ loans_ext_fields go with the earlier advance loan changelogs, before the DDS block that stays last. No file name is shared with dev-latest's 0594-0597 files. 0581_enable_loan_rate_register_maker_checker.xml: taken as on feature/advanceLoanProduct. It edits the existing 0581a/0581b changesets (CBSLOANBASEINTERESTRATE -> LOANBASEINTERESTRATE) and adds 0581c to rename rows already seeded; all three carry <validCheckSum>ANY</validCheckSum>, so databases that ran the old version still validate. CommandSourceService: Sonali's LOANBASEINTERESTRATE, DELINQUENCY_BUCKET and DELINQUENCY_RANGE checks sit alongside the DDS isDdsEntity check. Nothing from dev-latest, DDS or Sonali is removed; no file deleted; no DB password change. Verified with :fineract-provider:compileJava. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+3
@@ -157,6 +157,9 @@ public class CommandSourceService {
|
||||
}
|
||||
if ("GLACCOUNT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "CBSBASEINTERESTRATE".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "LOANBASEINTERESTRATE".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "DELINQUENCY_BUCKET".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "DELINQUENCY_RANGE".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "CHARGE".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "LOANPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|| "SAVINGSPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||
|
||||
+2
-2
@@ -2460,9 +2460,9 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createCbsLoanBaseInterestRate() {
|
||||
public CommandWrapperBuilder createLoanBaseInterestRate() {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "CBSLOANBASEINTERESTRATE";
|
||||
this.entityName = "LOANBASEINTERESTRATE";
|
||||
this.entityId = null;
|
||||
this.href = "/loanproducts/baseinterestrates";
|
||||
return this;
|
||||
|
||||
+169
@@ -295,6 +295,175 @@ public class LoanProductAdvanceExt {
|
||||
@Column(name = "cheque_block_lien_released")
|
||||
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) {
|
||||
this.loanProduct = loanProduct;
|
||||
this.loanProductId = loanProduct.getId();
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDateTime;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* A "particular" (loan type) row of the Loan Interest Rate Register chart. The GL account is a
|
||||
* property of the particular, so it lives here rather than on each rate-register version. Kept as a
|
||||
* plain {@code gl_account_id} column (no relation) to avoid pulling the accounting module's entity
|
||||
* graph into the loan module.
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "m_loan_rate_register_product")
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class LoanRateRegisterProduct {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "sr_no", nullable = false)
|
||||
private Integer srNo;
|
||||
|
||||
@Column(name = "name", length = 200, nullable = false)
|
||||
private String name;
|
||||
|
||||
@Column(name = "gl_account_id")
|
||||
private Long glAccountId;
|
||||
|
||||
@Column(name = "is_active", nullable = false)
|
||||
private Boolean isActive;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private LocalDateTime createdDate;
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 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 LoanRateRegisterProductRepository extends JpaRepository<LoanRateRegisterProduct, Long> {
|
||||
|
||||
/** Active particulars in chart order. */
|
||||
List<LoanRateRegisterProduct> findByIsActiveTrueOrderBySrNoAsc();
|
||||
}
|
||||
+8
-3
@@ -41,6 +41,10 @@ public class LoanRateRegisterSlabHistory {
|
||||
private Long effectiveId;
|
||||
|
||||
// ---- product dimension ----------------------------------------------
|
||||
/** Stable link to the master particular (and its GL). Null only for rows written before the master existed. */
|
||||
@Column(name = "product_id")
|
||||
private Long productId;
|
||||
|
||||
@Column(name = "product_sr_no", nullable = false)
|
||||
private Integer productSrNo;
|
||||
|
||||
@@ -73,11 +77,12 @@ public class LoanRateRegisterSlabHistory {
|
||||
@Column(name = "created_date")
|
||||
private LocalDateTime createdDate;
|
||||
|
||||
public static LoanRateRegisterSlabHistory create(final Long effectiveId, final Integer productSrNo, final String productName,
|
||||
final Integer slabOrder, final String slabLabel, final BigDecimal amountUpto, final BigDecimal rateValue,
|
||||
final String rateFormula, final boolean isFormula, final boolean isNa) {
|
||||
public static LoanRateRegisterSlabHistory create(final Long effectiveId, final Long productId, final Integer productSrNo,
|
||||
final String productName, final Integer slabOrder, final String slabLabel, final BigDecimal amountUpto,
|
||||
final BigDecimal rateValue, final String rateFormula, final boolean isFormula, final boolean isNa) {
|
||||
final LoanRateRegisterSlabHistory s = new LoanRateRegisterSlabHistory();
|
||||
s.effectiveId = effectiveId;
|
||||
s.productId = productId;
|
||||
s.productSrNo = productSrNo;
|
||||
s.productName = productName;
|
||||
s.slabOrder = slabOrder;
|
||||
|
||||
+50
-1
@@ -37,10 +37,15 @@ import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.Context;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.MultivaluedMap;
|
||||
import jakarta.ws.rs.core.UriInfo;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.PortfolioCommandSourceWritePlatformService;
|
||||
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.configuration.domain.ConfigurationDomainService;
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@Path("template")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
|
||||
+15
-3
@@ -44,7 +44,7 @@ import org.springframework.stereotype.Component;
|
||||
* a POST that goes through Fineract's maker-checker pipeline.
|
||||
*
|
||||
* Reads are gated by the pre-existing {@code LOANPRODUCT} resource; writes by the two seeded
|
||||
* permissions {@code CREATE_CBSLOANBASEINTERESTRATE} (maker) and {@code CREATE_CBSLOANBASEINTERESTRATE_CHECKER}.
|
||||
* permissions {@code CREATE_LOANBASEINTERESTRATE} (maker) and {@code CREATE_LOANBASEINTERESTRATE_CHECKER}.
|
||||
*/
|
||||
@Path("/v1/loanproducts/baseinterestrates")
|
||||
@Component
|
||||
@@ -76,6 +76,18 @@ public class LoanProductsBaseInterestRatesApiResource {
|
||||
return this.gson.toJson(data);
|
||||
}
|
||||
|
||||
/** Master list of particulars (loan types) that make up the chart rows. Literal path wins over {id}. */
|
||||
@GET
|
||||
@Path("products")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Get Loan Interest Rate Register particulars", description = "Returns the master list of loan types (chart rows) with their mapped GL account id, in chart order.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
public String retrieveProducts() {
|
||||
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
return this.gson.toJson(this.readService.retrieveProducts());
|
||||
}
|
||||
|
||||
/** The currently in-force version, with its full chart + CIBIL. */
|
||||
@GET
|
||||
@Path("active")
|
||||
@@ -103,7 +115,7 @@ public class LoanProductsBaseInterestRatesApiResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit a new version. If {@code CREATE_CBSLOANBASEINTERESTRATE.can_maker_checker=true}, this
|
||||
* Submit a new version. If {@code CREATE_LOANBASEINTERESTRATE.can_maker_checker=true}, this
|
||||
* returns {@code rollbackTransaction=true} and the row sits in {@code m_portfolio_command_source}
|
||||
* until a checker approves it from the Approvals tab. Otherwise it commits immediately.
|
||||
*/
|
||||
@@ -114,7 +126,7 @@ public class LoanProductsBaseInterestRatesApiResource {
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
public CommandProcessingResult submitRateRegister(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||
.createCbsLoanBaseInterestRate()
|
||||
.createLoanBaseInterestRate()
|
||||
.withJson(apiRequestBodyAsJson)
|
||||
.build();
|
||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
+14
@@ -30,9 +30,23 @@ public class LoanRateRegisterSaveRequest {
|
||||
private String fallbackBelowMin;
|
||||
private String note;
|
||||
|
||||
/**
|
||||
* Particulars (chart rows) with their GL. A row whose srNo is not yet in the master is created on
|
||||
* approval and must carry a glAccountId; for an existing row a changed glAccountId/name updates the master.
|
||||
*/
|
||||
private List<ProductRequest> products;
|
||||
private List<SlabRequest> slabs;
|
||||
private List<CibilSlabRequest> cibilSlabs;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class ProductRequest {
|
||||
private Integer srNo;
|
||||
private String name;
|
||||
private Long glAccountId;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
/** Response DTO for one particular (loan type) of the Loan Interest Rate Register. */
|
||||
@Getter
|
||||
@Builder
|
||||
public class LoanRateRegisterProductData {
|
||||
|
||||
private Long id;
|
||||
private Integer srNo;
|
||||
private String name;
|
||||
/** Null until the bank maps the particular to a GL account. */
|
||||
private Long glAccountId;
|
||||
private Boolean isActive;
|
||||
}
|
||||
+1
@@ -20,6 +20,7 @@ import lombok.Getter;
|
||||
public class LoanRateRegisterSlabData {
|
||||
|
||||
private Long id;
|
||||
private Long productId;
|
||||
private Integer productSrNo;
|
||||
private String productName;
|
||||
private Integer slabOrder;
|
||||
|
||||
+4
-4
@@ -17,15 +17,15 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Dispatched by Fineract when a {@code CBSLOANBASEINTERESTRATE} / {@code CREATE} command is executed —
|
||||
* Dispatched by Fineract when a {@code LOANBASEINTERESTRATE} / {@code CREATE} command is executed —
|
||||
* on first submit (maker-checker off) or on checker approval (maker-checker on). The {@code @CommandType}
|
||||
* entity string here MUST match the one registered by {@code CommandWrapperBuilder.createCbsLoanBaseInterestRate()}
|
||||
* entity string here MUST match the one registered by {@code CommandWrapperBuilder.createLoanBaseInterestRate()}
|
||||
* and the permission codes seeded by {@code 0581_enable_loan_rate_register_maker_checker.xml}.
|
||||
*/
|
||||
@Service
|
||||
@CommandType(entity = "CBSLOANBASEINTERESTRATE", action = "CREATE")
|
||||
@CommandType(entity = "LOANBASEINTERESTRATE", action = "CREATE")
|
||||
@RequiredArgsConstructor
|
||||
public class CreateCbsLoanBaseInterestRateCommandHandler implements NewCommandSourceHandler {
|
||||
public class CreateLoanBaseInterestRateCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final LoanRateRegisterWritePlatformService writePlatformService;
|
||||
|
||||
+142
-1
@@ -61,7 +61,22 @@ public class LoanProductAdvanceExtService {
|
||||
"chequeBookSizes", "chequeMaxBooksOutstanding", "chequeReissueWhenLeaves", "chequeFirstBookFree",
|
||||
"chequeFreeBooksPerYear", "chequeNamePrinted", "chequePrintDescription", "chequeStopPaymentAllowed",
|
||||
"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
|
||||
@@ -84,6 +99,18 @@ public class LoanProductAdvanceExtService {
|
||||
.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) {
|
||||
for (final String param : ADVANCE_PARAMETERS) {
|
||||
if (command.parameterExists(param)) {
|
||||
@@ -205,6 +232,63 @@ public class LoanProductAdvanceExtService {
|
||||
setBoolean(command, "chequeBlockFrozen", ext::getChequeBlockFrozen, ext::setChequeBlockFrozen, changes);
|
||||
setBoolean(command, "chequeBlockLienReleased", ext::getChequeBlockLienReleased, ext::setChequeBlockLienReleased,
|
||||
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 ----------
|
||||
@@ -368,6 +452,63 @@ public class LoanProductAdvanceExtService {
|
||||
map.put("chequeBlockOverDp", ext.getChequeBlockOverDp());
|
||||
map.put("chequeBlockFrozen", ext.getChequeBlockFrozen());
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -10,6 +10,7 @@ package org.apache.fineract.portfolio.loanproduct.service;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterData;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterProductData;
|
||||
|
||||
public interface LoanRateRegisterReadPlatformService {
|
||||
|
||||
@@ -21,4 +22,7 @@ public interface LoanRateRegisterReadPlatformService {
|
||||
|
||||
/** A specific version by id. */
|
||||
Optional<LoanRateRegisterData> retrieveById(Long id);
|
||||
|
||||
/** Master list of particulars (loan types) that make up the chart rows, in chart order. */
|
||||
List<LoanRateRegisterProductData> retrieveProducts();
|
||||
}
|
||||
|
||||
+21
@@ -13,11 +13,14 @@ import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterCibilSlabData;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterData;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterProductData;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterSlabData;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlab;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlabRepository;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffective;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffectiveRepository;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterProductRepository;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterSlabHistory;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterSlabHistoryRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -33,6 +36,23 @@ public class LoanRateRegisterReadPlatformServiceImpl implements LoanRateRegister
|
||||
private final LoanRateRegisterEffectiveRepository effectiveRepository;
|
||||
private final LoanRateRegisterSlabHistoryRepository slabRepository;
|
||||
private final LoanRateRegisterCibilSlabRepository cibilRepository;
|
||||
private final LoanRateRegisterProductRepository productRepository;
|
||||
|
||||
@Override
|
||||
public List<LoanRateRegisterProductData> retrieveProducts() {
|
||||
final List<LoanRateRegisterProduct> products = this.productRepository.findByIsActiveTrueOrderBySrNoAsc();
|
||||
final List<LoanRateRegisterProductData> out = new ArrayList<>(products.size());
|
||||
for (final LoanRateRegisterProduct p : products) {
|
||||
out.add(LoanRateRegisterProductData.builder()
|
||||
.id(p.getId())
|
||||
.srNo(p.getSrNo())
|
||||
.name(p.getName())
|
||||
.glAccountId(p.getGlAccountId())
|
||||
.isActive(p.getIsActive())
|
||||
.build());
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<LoanRateRegisterData> retrieveAll() {
|
||||
@@ -65,6 +85,7 @@ public class LoanRateRegisterReadPlatformServiceImpl implements LoanRateRegister
|
||||
for (final LoanRateRegisterSlabHistory c : cells) {
|
||||
slabData.add(LoanRateRegisterSlabData.builder()
|
||||
.id(c.getId())
|
||||
.productId(c.getProductId())
|
||||
.productSrNo(c.getProductSrNo())
|
||||
.productName(c.getProductName())
|
||||
.slabOrder(c.getSlabOrder())
|
||||
|
||||
+101
-4
@@ -10,21 +10,32 @@ package org.apache.fineract.portfolio.loanproduct.service;
|
||||
import com.google.gson.Gson;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.loanproduct.api.LoanRateRegisterSaveRequest;
|
||||
import org.apache.fineract.portfolio.loanproduct.api.LoanRateRegisterSaveRequest.CibilSlabRequest;
|
||||
import org.apache.fineract.portfolio.loanproduct.api.LoanRateRegisterSaveRequest.ProductRequest;
|
||||
import org.apache.fineract.portfolio.loanproduct.api.LoanRateRegisterSaveRequest.SlabRequest;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlab;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlabRepository;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffective;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffectiveRepository;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterProductRepository;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterSlabHistory;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterSlabHistoryRepository;
|
||||
import org.apache.fineract.portfolio.loanproduct.serialization.LoanRateRegisterDataValidator;
|
||||
@@ -45,6 +56,8 @@ public class LoanRateRegisterWritePlatformServiceImpl implements LoanRateRegiste
|
||||
private final LoanRateRegisterEffectiveRepository effectiveRepository;
|
||||
private final LoanRateRegisterSlabHistoryRepository slabRepository;
|
||||
private final LoanRateRegisterCibilSlabRepository cibilRepository;
|
||||
private final LoanRateRegisterProductRepository productRepository;
|
||||
private final GLAccountRepositoryWrapper glAccountRepositoryWrapper;
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Override
|
||||
@@ -56,6 +69,10 @@ public class LoanRateRegisterWritePlatformServiceImpl implements LoanRateRegiste
|
||||
final LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||
final LocalDate effectiveFrom = parseDateOrToday(req.getEffectiveFromDate(), today);
|
||||
|
||||
// 0) Upsert the particulars master first, so a missing GL on a new product fails the whole
|
||||
// save before any version row is touched. Returns srNo -> master row for the cells below.
|
||||
final Map<Integer, LoanRateRegisterProduct> productsBySrNo = upsertProducts(req);
|
||||
|
||||
// 1) Deactivate the currently-active version (if any).
|
||||
final List<LoanRateRegisterEffective> active = this.effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc();
|
||||
for (final LoanRateRegisterEffective prior : active) {
|
||||
@@ -65,17 +82,20 @@ public class LoanRateRegisterWritePlatformServiceImpl implements LoanRateRegiste
|
||||
this.effectiveRepository.saveAll(active);
|
||||
}
|
||||
|
||||
// 2) Insert the new effective row.
|
||||
// 2) Insert the new effective row. saveAndFlush ensures Hibernate executes the INSERT
|
||||
// immediately so the auto-generated id is populated before we use it as the FK on the
|
||||
// child slab/CIBIL rows (same pattern as FD side's FdBaseInterestRateReadPlatformServiceImpl).
|
||||
final LoanRateRegisterEffective fresh = LoanRateRegisterEffective.create(req.getName(), effectiveFrom,
|
||||
req.getCurrencyCode(), req.getFallbackNoCibilSlab(), req.getFallbackBelowMin(), req.getNote(), createdById);
|
||||
final LoanRateRegisterEffective saved = this.effectiveRepository.save(fresh);
|
||||
final LoanRateRegisterEffective saved = this.effectiveRepository.saveAndFlush(fresh);
|
||||
final Long versionId = saved.getId();
|
||||
|
||||
// 3) Insert all chart cells for this version.
|
||||
final List<LoanRateRegisterSlabHistory> cells = new ArrayList<>(req.getSlabs().size());
|
||||
for (final SlabRequest s : req.getSlabs()) {
|
||||
cells.add(LoanRateRegisterSlabHistory.create(versionId, s.getProductSrNo(), s.getProductName(), s.getSlabOrder(),
|
||||
s.getSlabLabel(), s.getAmountUpto(), s.getRateValue(), s.getRateFormula(),
|
||||
final Long productId = productsBySrNo.get(s.getProductSrNo()).getId();
|
||||
cells.add(LoanRateRegisterSlabHistory.create(versionId, productId, s.getProductSrNo(), s.getProductName(),
|
||||
s.getSlabOrder(), s.getSlabLabel(), s.getAmountUpto(), s.getRateValue(), s.getRateFormula(),
|
||||
Boolean.TRUE.equals(s.getIsFormula()), Boolean.TRUE.equals(s.getIsNa())));
|
||||
}
|
||||
this.slabRepository.saveAll(cells);
|
||||
@@ -94,6 +114,83 @@ public class LoanRateRegisterWritePlatformServiceImpl implements LoanRateRegiste
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure every particular used by the submitted chart exists in the master. A particular not
|
||||
* yet in the master is created and must carry a valid GL account; for an existing one a changed
|
||||
* GL or name is applied. A particular that still has no GL after this (seeded rows the bank has
|
||||
* not mapped yet) rejects the whole save.
|
||||
*/
|
||||
private Map<Integer, LoanRateRegisterProduct> upsertProducts(final LoanRateRegisterSaveRequest req) {
|
||||
final Map<Integer, LoanRateRegisterProduct> bySrNo = new HashMap<>();
|
||||
for (final LoanRateRegisterProduct p : this.productRepository.findAll()) {
|
||||
bySrNo.put(p.getSrNo(), p);
|
||||
}
|
||||
final Map<Integer, ProductRequest> requested = new HashMap<>();
|
||||
if (req.getProducts() != null) {
|
||||
for (final ProductRequest pr : req.getProducts()) {
|
||||
requested.put(pr.getSrNo(), pr);
|
||||
}
|
||||
}
|
||||
// Every srNo the chart uses, with a label taken from the first cell that mentions it.
|
||||
final Set<Integer> used = new LinkedHashSet<>();
|
||||
final Map<Integer, String> cellLabel = new HashMap<>();
|
||||
for (final SlabRequest s : req.getSlabs()) {
|
||||
used.add(s.getProductSrNo());
|
||||
cellLabel.putIfAbsent(s.getProductSrNo(), s.getProductName());
|
||||
}
|
||||
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
final List<LoanRateRegisterProduct> toSave = new ArrayList<>();
|
||||
for (final Integer srNo : used) {
|
||||
final ProductRequest pr = requested.get(srNo);
|
||||
LoanRateRegisterProduct existing = bySrNo.get(srNo);
|
||||
final String name = pr != null && pr.getName() != null && !pr.getName().isBlank() ? pr.getName() : cellLabel.get(srNo);
|
||||
if (existing == null) {
|
||||
if (pr == null || pr.getGlAccountId() == null) {
|
||||
errors.add(ApiParameterError.parameterError("validation.loanRateRegister.product.gl.required",
|
||||
"New product '" + name + "' (Sr. " + srNo + ") needs a GL account", "products[" + srNo + "].glAccountId"));
|
||||
continue;
|
||||
}
|
||||
this.glAccountRepositoryWrapper.findOneWithNotFoundDetection(pr.getGlAccountId());
|
||||
existing = new LoanRateRegisterProduct();
|
||||
existing.setSrNo(srNo);
|
||||
existing.setName(name);
|
||||
existing.setGlAccountId(pr.getGlAccountId());
|
||||
existing.setIsActive(true);
|
||||
existing.setCreatedDate(LocalDateTime.now(DateUtils.getDateTimeZoneOfTenant()));
|
||||
toSave.add(existing);
|
||||
} else if (pr != null) {
|
||||
boolean changed = false;
|
||||
if (pr.getGlAccountId() != null && !pr.getGlAccountId().equals(existing.getGlAccountId())) {
|
||||
this.glAccountRepositoryWrapper.findOneWithNotFoundDetection(pr.getGlAccountId());
|
||||
existing.setGlAccountId(pr.getGlAccountId());
|
||||
changed = true;
|
||||
}
|
||||
if (name != null && !name.equals(existing.getName())) {
|
||||
existing.setName(name);
|
||||
changed = true;
|
||||
}
|
||||
if (changed) {
|
||||
toSave.add(existing);
|
||||
}
|
||||
}
|
||||
// Every particular in a submitted chart must end up with a GL, including the seeded ones.
|
||||
if (existing.getGlAccountId() == null) {
|
||||
errors.add(ApiParameterError.parameterError("validation.loanRateRegister.product.gl.required",
|
||||
"Product '" + existing.getName() + "' (Sr. " + srNo + ") needs a GL account", "products[" + srNo + "].glAccountId"));
|
||||
continue;
|
||||
}
|
||||
bySrNo.put(srNo, existing);
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.loanRateRegister.failed", "Validation errors exist.", errors);
|
||||
}
|
||||
for (final LoanRateRegisterProduct p : this.productRepository.saveAllAndFlush(toSave)) {
|
||||
bySrNo.put(p.getSrNo(), p);
|
||||
}
|
||||
return bySrNo;
|
||||
}
|
||||
|
||||
private static LocalDate parseDateOrToday(final String iso, final LocalDate today) {
|
||||
if (iso == null || iso.isBlank()) {
|
||||
return today;
|
||||
|
||||
@@ -619,6 +619,10 @@
|
||||
<include file="parts/0572_add_loan_product_advance_ext_fields.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0580_create_loan_rate_register_tables.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0581_enable_loan_rate_register_maker_checker.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/0596_enable_delinquency_maker_checker.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0597_add_loan_product_loans_ext_fields.xml" relativeToChangelogFile="true"/>
|
||||
|
||||
<!-- DDS: the scheme's own tables, settings and permissions. Kept together and last, because
|
||||
each one builds on the core the block above sets up and none of it builds on DDS. -->
|
||||
|
||||
+46
-12
@@ -28,29 +28,63 @@
|
||||
Enable maker-checker on the Loan Interest Rate Register. Mirrors 0470 for the FD side.
|
||||
Two permissions: the maker's gated with can_maker_checker=true so submits land in
|
||||
m_portfolio_command_source as PENDING; the checker approves from the standard Checker Inbox
|
||||
via /makercheckers/{id}?command=approve, which fires CreateCbsLoanBaseInterestRateCommandHandler.
|
||||
via /makercheckers/{id}?command=approve, which fires CreateLoanBaseInterestRateCommandHandler.
|
||||
|
||||
Permission codes use the LOANBASEINTERESTRATE entity (no "CBS" prefix) to match the clean
|
||||
naming convention requested for the Loan module.
|
||||
-->
|
||||
|
||||
<changeSet id="0581a_loan_rate_register_maker_checker_permissions" author="cbsnexora" runOnChange="false">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<sql>
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'portfolio', 'CREATE_CBSLOANBASEINTERESTRATE', 'CBSLOANBASEINTERESTRATE', 'CREATE', true
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_CBSLOANBASEINTERESTRATE');
|
||||
-- Rename any previously-seeded CBS-prefixed rows (safe no-op if a fresh DB).
|
||||
UPDATE m_permission
|
||||
SET code = 'CREATE_LOANBASEINTERESTRATE', entity_name = 'LOANBASEINTERESTRATE'
|
||||
WHERE code = 'CREATE_CBSLOANBASEINTERESTRATE';
|
||||
|
||||
UPDATE m_permission
|
||||
SET code = 'CREATE_LOANBASEINTERESTRATE_CHECKER', entity_name = 'LOANBASEINTERESTRATE'
|
||||
WHERE code = 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER';
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'portfolio', 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER', 'CBSLOANBASEINTERESTRATE', 'CREATE_CHECKER', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER');
|
||||
SELECT 'portfolio', 'CREATE_LOANBASEINTERESTRATE', 'LOANBASEINTERESTRATE', 'CREATE', true
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_LOANBASEINTERESTRATE');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'portfolio', 'CREATE_LOANBASEINTERESTRATE_CHECKER', 'LOANBASEINTERESTRATE', 'CREATE_CHECKER', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_LOANBASEINTERESTRATE_CHECKER');
|
||||
|
||||
UPDATE m_permission
|
||||
SET can_maker_checker = TRUE
|
||||
WHERE entity_name = 'CBSLOANBASEINTERESTRATE'
|
||||
WHERE entity_name = 'LOANBASEINTERESTRATE'
|
||||
AND action_name = 'CREATE';
|
||||
|
||||
UPDATE m_permission
|
||||
SET action_name = 'CREATE_CHECKER'
|
||||
WHERE entity_name = 'CBSLOANBASEINTERESTRATE'
|
||||
AND code = 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER';
|
||||
WHERE entity_name = 'LOANBASEINTERESTRATE'
|
||||
AND code = 'CREATE_LOANBASEINTERESTRATE_CHECKER';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- Separate changeset to force the CBS→LOAN rename on DBs that already applied 0581a with the
|
||||
old names. Liquibase won't re-run an already-applied changeset, so the UPDATE statements
|
||||
inside 0581a don't fire a second time; this new id runs once per DB on next startup. -->
|
||||
<changeSet id="0581c_rename_cbs_prefix_to_loanbaseinterestrate" author="cbsnexora" runOnChange="false">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<sql>
|
||||
UPDATE m_permission
|
||||
SET code = 'CREATE_LOANBASEINTERESTRATE', entity_name = 'LOANBASEINTERESTRATE'
|
||||
WHERE code = 'CREATE_CBSLOANBASEINTERESTRATE';
|
||||
|
||||
UPDATE m_permission
|
||||
SET code = 'CREATE_LOANBASEINTERESTRATE_CHECKER', entity_name = 'LOANBASEINTERESTRATE'
|
||||
WHERE code = 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER';
|
||||
|
||||
-- Rename any pending command-source rows still carrying the old entity string so
|
||||
-- the handler picks them up after the restart.
|
||||
UPDATE m_portfolio_command_source
|
||||
SET entity_name = 'LOANBASEINTERESTRATE'
|
||||
WHERE entity_name = 'CBSLOANBASEINTERESTRATE';
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
@@ -63,7 +97,7 @@
|
||||
FROM m_role r
|
||||
CROSS JOIN m_permission p
|
||||
WHERE r.name = 'Super user'
|
||||
AND p.code IN ('CREATE_CBSLOANBASEINTERESTRATE', 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER')
|
||||
AND p.code IN ('CREATE_LOANBASEINTERESTRATE', 'CREATE_LOANBASEINTERESTRATE_CHECKER')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
@@ -76,7 +110,7 @@
|
||||
JOIN m_appuser_role ur ON ur.appuser_id = u.id
|
||||
CROSS JOIN m_permission p
|
||||
WHERE u.username = 'bankadmin'
|
||||
AND p.code IN ('CREATE_CBSLOANBASEINTERESTRATE', 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER')
|
||||
AND p.code IN ('CREATE_LOANBASEINTERESTRATE', 'CREATE_LOANBASEINTERESTRATE_CHECKER')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = ur.role_id AND rp.permission_id = p.id
|
||||
@@ -88,7 +122,7 @@
|
||||
FROM m_role r
|
||||
CROSS JOIN m_permission p
|
||||
WHERE LOWER(r.name) = 'clerk'
|
||||
AND p.code = 'CREATE_CBSLOANBASEINTERESTRATE'
|
||||
AND p.code = 'CREATE_LOANBASEINTERESTRATE'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
Master list of "particulars" (loan types) that appear as rows in the Loan Interest Rate
|
||||
Register chart. The GL account belongs to the particular, not to a version, so it is kept
|
||||
here and never has to be re-selected when a new rate version is created.
|
||||
|
||||
Seeded with the ~16 loan types that almost every bank offers. Bank-specific particulars are
|
||||
added later through Loan Interest Rate Register > Edit (new version) > Add product, which
|
||||
requires a GL. gl_account_id is NULL for the seeded rows until the bank maps them.
|
||||
-->
|
||||
|
||||
<changeSet id="0594a_create_loan_rate_register_product" author="cbsnexora">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_loan_rate_register_product"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_loan_rate_register_product">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="sr_no" type="INT">
|
||||
<constraints nullable="false" unique="true" uniqueConstraintName="uq_mlrrp_sr_no"/>
|
||||
</column>
|
||||
<column name="name" type="VARCHAR(200)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="gl_account_id" type="BIGINT"/>
|
||||
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="created_date" type="DATETIME"/>
|
||||
</createTable>
|
||||
<addForeignKeyConstraint baseTableName="m_loan_rate_register_product"
|
||||
baseColumnNames="gl_account_id"
|
||||
constraintName="fk_mlrrp_gl_account"
|
||||
referencedTableName="acc_gl_account"
|
||||
referencedColumnNames="id"/>
|
||||
</changeSet>
|
||||
|
||||
<!-- Link chart cells to the particular by id. product_sr_no / product_name stay on the row as the
|
||||
denormalised label of that version; product_id is the stable link to the master (and its GL).
|
||||
Nullable so rows written before this changeset survive; backfilled by sr_no below. -->
|
||||
<changeSet id="0594c_loan_rate_register_slab_history_product_id" author="cbsnexora">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_loan_rate_register_slab_history" columnName="product_id"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_loan_rate_register_slab_history">
|
||||
<column name="product_id" type="BIGINT"/>
|
||||
</addColumn>
|
||||
<addForeignKeyConstraint baseTableName="m_loan_rate_register_slab_history"
|
||||
baseColumnNames="product_id"
|
||||
constraintName="fk_mlrrsh_product"
|
||||
referencedTableName="m_loan_rate_register_product"
|
||||
referencedColumnNames="id"/>
|
||||
<createIndex indexName="idx_mlrrsh_product" tableName="m_loan_rate_register_slab_history">
|
||||
<column name="product_id"/>
|
||||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0594d_backfill_loan_rate_register_slab_history_product_id" author="cbsnexora">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<sql>
|
||||
UPDATE m_loan_rate_register_slab_history
|
||||
SET product_id = (SELECT p.id FROM m_loan_rate_register_product p
|
||||
WHERE p.sr_no = m_loan_rate_register_slab_history.product_sr_no)
|
||||
WHERE product_id IS NULL;
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0594b_seed_loan_rate_register_default_products" author="cbsnexora">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_loan_rate_register_product</sqlCheck>
|
||||
</preConditions>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="1"/>
|
||||
<column name="name" value="Housing Loan (Purchase/Construction)"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="2"/>
|
||||
<column name="name" value="Home Repair/Renovation"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="3"/>
|
||||
<column name="name" value="Personal Loan (Unsecured)"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="4"/>
|
||||
<column name="name" value="Personal Loan (Secured/Property)"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="5"/>
|
||||
<column name="name" value="Two Wheeler Loan"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="6"/>
|
||||
<column name="name" value="Car Loan (New)"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="7"/>
|
||||
<column name="name" value="Used Car/Vehicle Loan"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="8"/>
|
||||
<column name="name" value="Commercial Vehicle Loan"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="9"/>
|
||||
<column name="name" value="Gold Loan"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="10"/>
|
||||
<column name="name" value="Business Term Loan"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="11"/>
|
||||
<column name="name" value="Machinery & Equipment Loan"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="12"/>
|
||||
<column name="name" value="Cash Credit"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="13"/>
|
||||
<column name="name" value="Overdraft"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="14"/>
|
||||
<column name="name" value="Agriculture Loan"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="15"/>
|
||||
<column name="name" value="Loan against FDR"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
<insert tableName="m_loan_rate_register_product">
|
||||
<column name="sr_no" valueNumeric="16"/>
|
||||
<column name="name" value="Loan against RD"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="created_date" valueComputed="NOW()"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
Loan portfolio (asset) GL accounts for the Loan Interest Rate Register particulars that had no
|
||||
GL in the standardized chart of accounts (0503). Existing GLs are reused for the rest:
|
||||
LOAN AGAINST DEPOSITE 100301, CASH CREDIT 100303, LOAN AGAINST PROPERTY 100401,
|
||||
PERSONAL LOAN 100402, HOUSE LOAN 100501.
|
||||
|
||||
Each row is inserted under its existing header (SHORT/MEDIUM/LONG TERM LOAN) and only when that
|
||||
header exists, so tenants with a different chart of accounts are left untouched. Codes follow the
|
||||
0503 scheme: classification(1) + group(2) + sequence(3). Idempotent on gl_code.
|
||||
-->
|
||||
<changeSet id="0595_seed_loan_rate_register_gl_accounts" author="cbsnexora">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<sql>
|
||||
-- SHORT TERM LOAN (100300)
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'OVERDRAFT', '100304', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100300' AND p.name = 'SHORT TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100304');
|
||||
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'GOLD LOAN', '100305', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100300' AND p.name = 'SHORT TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100305');
|
||||
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'AGRICULTURE LOAN', '100306', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100300' AND p.name = 'SHORT TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100306');
|
||||
|
||||
-- MEDIUM TERM LOAN (100400)
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'TWO WHEELER LOAN', '100403', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100400' AND p.name = 'MEDIUM TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100403');
|
||||
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'CAR LOAN (NEW)', '100404', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100400' AND p.name = 'MEDIUM TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100404');
|
||||
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'USED CAR / VEHICLE LOAN', '100405', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100400' AND p.name = 'MEDIUM TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100405');
|
||||
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'COMMERCIAL VEHICLE LOAN', '100406', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100400' AND p.name = 'MEDIUM TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100406');
|
||||
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'BUSINESS TERM LOAN', '100407', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100400' AND p.name = 'MEDIUM TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100407');
|
||||
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'MACHINERY & EQUIPMENT LOAN', '100408', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100400' AND p.name = 'MEDIUM TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100408');
|
||||
|
||||
-- LONG TERM LOAN (100500)
|
||||
INSERT INTO acc_gl_account (name, gl_code, disabled, manual_journal_entries_allowed, account_usage, classification_enum, parent_id)
|
||||
SELECT 'HOME REPAIR / RENOVATION LOAN', '100502', false, true, 1, 1, p.id FROM acc_gl_account p
|
||||
WHERE p.gl_code = '100500' AND p.name = 'LONG TERM LOAN'
|
||||
AND NOT EXISTS (SELECT 1 FROM acc_gl_account WHERE gl_code = '100502');
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
<?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">
|
||||
|
||||
<!--
|
||||
Maker-checker on Delinquency Ranges and Buckets (the SMA / NPA classification master).
|
||||
|
||||
A bucket or range feeds the SMA / NPA class of every loan product linked to it, so create, update
|
||||
and delete must not take effect on one person's click. The six commands (CREATE / UPDATE / DELETE on
|
||||
DELINQUENCY_RANGE and DELINQUENCY_BUCKET) are gated with can_maker_checker = true, so each lands in
|
||||
m_portfolio_command_source as PENDING and is applied only when a checker approves it from the
|
||||
Checker Inbox. Each gets a matching *_CHECKER permission, which approving requires.
|
||||
|
||||
Roles: create / update / delete (maker) and the checker permissions go to Super user and the
|
||||
bankadmin user's roles only. UPDATE and DELETE are taken away from every other role. CREATE is left
|
||||
as it is for roles that already hold it. Widen or narrow this later from Admin > Roles.
|
||||
-->
|
||||
|
||||
<changeSet id="0596a_delinquency_maker_checker_permissions" author="cbsnexora" runOnChange="false">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<sql>
|
||||
UPDATE m_permission
|
||||
SET can_maker_checker = TRUE
|
||||
WHERE code IN ('CREATE_DELINQUENCY_RANGE', 'UPDATE_DELINQUENCY_RANGE', 'DELETE_DELINQUENCY_RANGE',
|
||||
'CREATE_DELINQUENCY_BUCKET', 'UPDATE_DELINQUENCY_BUCKET', 'DELETE_DELINQUENCY_BUCKET');
|
||||
|
||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||
SELECT 'organisation', v.code, v.entity_name, v.action_name, false
|
||||
FROM (
|
||||
SELECT 'CREATE_DELINQUENCY_RANGE_CHECKER' AS code, 'DELINQUENCY_RANGE' AS entity_name, 'CREATE_CHECKER' AS action_name
|
||||
UNION ALL SELECT 'UPDATE_DELINQUENCY_RANGE_CHECKER', 'DELINQUENCY_RANGE', 'UPDATE_CHECKER'
|
||||
UNION ALL SELECT 'DELETE_DELINQUENCY_RANGE_CHECKER', 'DELINQUENCY_RANGE', 'DELETE_CHECKER'
|
||||
UNION ALL SELECT 'CREATE_DELINQUENCY_BUCKET_CHECKER', 'DELINQUENCY_BUCKET', 'CREATE_CHECKER'
|
||||
UNION ALL SELECT 'UPDATE_DELINQUENCY_BUCKET_CHECKER', 'DELINQUENCY_BUCKET', 'UPDATE_CHECKER'
|
||||
UNION ALL SELECT 'DELETE_DELINQUENCY_BUCKET_CHECKER', 'DELINQUENCY_BUCKET', 'DELETE_CHECKER'
|
||||
) v
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission p WHERE p.code = v.code);
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0596b_delinquency_permissions_to_admin_roles_only" author="cbsnexora" runOnChange="false">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<sql>
|
||||
-- Super user: maker + checker for all six commands.
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT r.id, p.id
|
||||
FROM m_role r
|
||||
CROSS JOIN m_permission p
|
||||
WHERE r.name = 'Super user'
|
||||
AND p.entity_name IN ('DELINQUENCY_RANGE', 'DELINQUENCY_BUCKET')
|
||||
AND p.action_name IN ('CREATE', 'UPDATE', 'DELETE', 'CREATE_CHECKER', 'UPDATE_CHECKER', 'DELETE_CHECKER')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
);
|
||||
|
||||
-- bankadmin user's role(s): maker + checker for all six commands.
|
||||
INSERT INTO m_role_permission (role_id, permission_id)
|
||||
SELECT DISTINCT ur.role_id, p.id
|
||||
FROM m_appuser u
|
||||
JOIN m_appuser_role ur ON ur.appuser_id = u.id
|
||||
CROSS JOIN m_permission p
|
||||
WHERE u.username = 'bankadmin'
|
||||
AND p.entity_name IN ('DELINQUENCY_RANGE', 'DELINQUENCY_BUCKET')
|
||||
AND p.action_name IN ('CREATE', 'UPDATE', 'DELETE', 'CREATE_CHECKER', 'UPDATE_CHECKER', 'DELETE_CHECKER')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_role_permission rp
|
||||
WHERE rp.role_id = ur.role_id AND rp.permission_id = p.id
|
||||
);
|
||||
|
||||
-- Every other role loses UPDATE / DELETE on ranges and buckets (and holds no checker permission).
|
||||
DELETE FROM m_role_permission
|
||||
WHERE permission_id IN (
|
||||
SELECT id FROM m_permission
|
||||
WHERE entity_name IN ('DELINQUENCY_RANGE', 'DELINQUENCY_BUCKET')
|
||||
AND action_name IN ('UPDATE', 'DELETE', 'CREATE_CHECKER', 'UPDATE_CHECKER', 'DELETE_CHECKER')
|
||||
)
|
||||
AND role_id NOT IN (
|
||||
SELECT id FROM m_role WHERE name = 'Super user'
|
||||
UNION
|
||||
SELECT ur.role_id
|
||||
FROM m_appuser u
|
||||
JOIN m_appuser_role ur ON ur.appuser_id = u.id
|
||||
WHERE u.username = 'bankadmin'
|
||||
);
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+90
@@ -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>
|
||||
Reference in New Issue
Block a user