feat(loan-products): add product master table and GL account seeding for loan interest rate registe
This commit is contained in:
+1
@@ -157,6 +157,7 @@ public class CommandSourceService {
|
|||||||
}
|
}
|
||||||
if ("GLACCOUNT".equalsIgnoreCase(commandSource.getEntityName())
|
if ("GLACCOUNT".equalsIgnoreCase(commandSource.getEntityName())
|
||||||
|| "CBSBASEINTERESTRATE".equalsIgnoreCase(commandSource.getEntityName())
|
|| "CBSBASEINTERESTRATE".equalsIgnoreCase(commandSource.getEntityName())
|
||||||
|
|| "LOANBASEINTERESTRATE".equalsIgnoreCase(commandSource.getEntityName())
|
||||||
|| "CHARGE".equalsIgnoreCase(commandSource.getEntityName())
|
|| "CHARGE".equalsIgnoreCase(commandSource.getEntityName())
|
||||||
|| "LOANPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
|| "LOANPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||||
|| "SAVINGSPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
|| "SAVINGSPRODUCT".equalsIgnoreCase(commandSource.getEntityName())
|
||||||
|
|||||||
+2
-2
@@ -2299,9 +2299,9 @@ public class CommandWrapperBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandWrapperBuilder createCbsLoanBaseInterestRate() {
|
public CommandWrapperBuilder createLoanBaseInterestRate() {
|
||||||
this.actionName = "CREATE";
|
this.actionName = "CREATE";
|
||||||
this.entityName = "CBSLOANBASEINTERESTRATE";
|
this.entityName = "LOANBASEINTERESTRATE";
|
||||||
this.entityId = null;
|
this.entityId = null;
|
||||||
this.href = "/loanproducts/baseinterestrates";
|
this.href = "/loanproducts/baseinterestrates";
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
+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;
|
private Long effectiveId;
|
||||||
|
|
||||||
// ---- product dimension ----------------------------------------------
|
// ---- 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)
|
@Column(name = "product_sr_no", nullable = false)
|
||||||
private Integer productSrNo;
|
private Integer productSrNo;
|
||||||
|
|
||||||
@@ -73,11 +77,12 @@ public class LoanRateRegisterSlabHistory {
|
|||||||
@Column(name = "created_date")
|
@Column(name = "created_date")
|
||||||
private LocalDateTime createdDate;
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
public static LoanRateRegisterSlabHistory create(final Long effectiveId, final Integer productSrNo, final String productName,
|
public static LoanRateRegisterSlabHistory create(final Long effectiveId, final Long productId, final Integer productSrNo,
|
||||||
final Integer slabOrder, final String slabLabel, final BigDecimal amountUpto, final BigDecimal rateValue,
|
final String productName, final Integer slabOrder, final String slabLabel, final BigDecimal amountUpto,
|
||||||
final String rateFormula, final boolean isFormula, final boolean isNa) {
|
final BigDecimal rateValue, final String rateFormula, final boolean isFormula, final boolean isNa) {
|
||||||
final LoanRateRegisterSlabHistory s = new LoanRateRegisterSlabHistory();
|
final LoanRateRegisterSlabHistory s = new LoanRateRegisterSlabHistory();
|
||||||
s.effectiveId = effectiveId;
|
s.effectiveId = effectiveId;
|
||||||
|
s.productId = productId;
|
||||||
s.productSrNo = productSrNo;
|
s.productSrNo = productSrNo;
|
||||||
s.productName = productName;
|
s.productName = productName;
|
||||||
s.slabOrder = slabOrder;
|
s.slabOrder = slabOrder;
|
||||||
|
|||||||
+15
-3
@@ -44,7 +44,7 @@ import org.springframework.stereotype.Component;
|
|||||||
* a POST that goes through Fineract's maker-checker pipeline.
|
* 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
|
* 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")
|
@Path("/v1/loanproducts/baseinterestrates")
|
||||||
@Component
|
@Component
|
||||||
@@ -76,6 +76,18 @@ public class LoanProductsBaseInterestRatesApiResource {
|
|||||||
return this.gson.toJson(data);
|
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. */
|
/** The currently in-force version, with its full chart + CIBIL. */
|
||||||
@GET
|
@GET
|
||||||
@Path("active")
|
@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}
|
* 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.
|
* 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") })
|
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||||
public CommandProcessingResult submitRateRegister(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
public CommandProcessingResult submitRateRegister(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||||
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
final CommandWrapper commandRequest = new CommandWrapperBuilder()
|
||||||
.createCbsLoanBaseInterestRate()
|
.createLoanBaseInterestRate()
|
||||||
.withJson(apiRequestBodyAsJson)
|
.withJson(apiRequestBodyAsJson)
|
||||||
.build();
|
.build();
|
||||||
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
return this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||||
|
|||||||
+14
@@ -30,9 +30,23 @@ public class LoanRateRegisterSaveRequest {
|
|||||||
private String fallbackBelowMin;
|
private String fallbackBelowMin;
|
||||||
private String note;
|
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<SlabRequest> slabs;
|
||||||
private List<CibilSlabRequest> cibilSlabs;
|
private List<CibilSlabRequest> cibilSlabs;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class ProductRequest {
|
||||||
|
private Integer srNo;
|
||||||
|
private String name;
|
||||||
|
private Long glAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@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 {
|
public class LoanRateRegisterSlabData {
|
||||||
|
|
||||||
private Long id;
|
private Long id;
|
||||||
|
private Long productId;
|
||||||
private Integer productSrNo;
|
private Integer productSrNo;
|
||||||
private String productName;
|
private String productName;
|
||||||
private Integer slabOrder;
|
private Integer slabOrder;
|
||||||
|
|||||||
+4
-4
@@ -17,15 +17,15 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
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}
|
* 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}.
|
* and the permission codes seeded by {@code 0581_enable_loan_rate_register_maker_checker.xml}.
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@CommandType(entity = "CBSLOANBASEINTERESTRATE", action = "CREATE")
|
@CommandType(entity = "LOANBASEINTERESTRATE", action = "CREATE")
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class CreateCbsLoanBaseInterestRateCommandHandler implements NewCommandSourceHandler {
|
public class CreateLoanBaseInterestRateCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
private final LoanRateRegisterWritePlatformService writePlatformService;
|
private final LoanRateRegisterWritePlatformService writePlatformService;
|
||||||
|
|
||||||
+4
@@ -10,6 +10,7 @@ package org.apache.fineract.portfolio.loanproduct.service;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterData;
|
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterData;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterProductData;
|
||||||
|
|
||||||
public interface LoanRateRegisterReadPlatformService {
|
public interface LoanRateRegisterReadPlatformService {
|
||||||
|
|
||||||
@@ -21,4 +22,7 @@ public interface LoanRateRegisterReadPlatformService {
|
|||||||
|
|
||||||
/** A specific version by id. */
|
/** A specific version by id. */
|
||||||
Optional<LoanRateRegisterData> retrieveById(Long 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 lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterCibilSlabData;
|
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterCibilSlabData;
|
||||||
import org.apache.fineract.portfolio.loanproduct.data.LoanRateRegisterData;
|
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.data.LoanRateRegisterSlabData;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlab;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlab;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlabRepository;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlabRepository;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffective;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffective;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffectiveRepository;
|
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.LoanRateRegisterSlabHistory;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterSlabHistoryRepository;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterSlabHistoryRepository;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -33,6 +36,23 @@ public class LoanRateRegisterReadPlatformServiceImpl implements LoanRateRegister
|
|||||||
private final LoanRateRegisterEffectiveRepository effectiveRepository;
|
private final LoanRateRegisterEffectiveRepository effectiveRepository;
|
||||||
private final LoanRateRegisterSlabHistoryRepository slabRepository;
|
private final LoanRateRegisterSlabHistoryRepository slabRepository;
|
||||||
private final LoanRateRegisterCibilSlabRepository cibilRepository;
|
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
|
@Override
|
||||||
public List<LoanRateRegisterData> retrieveAll() {
|
public List<LoanRateRegisterData> retrieveAll() {
|
||||||
@@ -65,6 +85,7 @@ public class LoanRateRegisterReadPlatformServiceImpl implements LoanRateRegister
|
|||||||
for (final LoanRateRegisterSlabHistory c : cells) {
|
for (final LoanRateRegisterSlabHistory c : cells) {
|
||||||
slabData.add(LoanRateRegisterSlabData.builder()
|
slabData.add(LoanRateRegisterSlabData.builder()
|
||||||
.id(c.getId())
|
.id(c.getId())
|
||||||
|
.productId(c.getProductId())
|
||||||
.productSrNo(c.getProductSrNo())
|
.productSrNo(c.getProductSrNo())
|
||||||
.productName(c.getProductName())
|
.productName(c.getProductName())
|
||||||
.slabOrder(c.getSlabOrder())
|
.slabOrder(c.getSlabOrder())
|
||||||
|
|||||||
+101
-4
@@ -10,21 +10,32 @@ package org.apache.fineract.portfolio.loanproduct.service;
|
|||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
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.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.CommandProcessingResult;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
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.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||||
import org.apache.fineract.portfolio.loanproduct.api.LoanRateRegisterSaveRequest;
|
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.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.api.LoanRateRegisterSaveRequest.SlabRequest;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlab;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlab;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlabRepository;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterCibilSlabRepository;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffective;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffective;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterEffectiveRepository;
|
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.LoanRateRegisterSlabHistory;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterSlabHistoryRepository;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanRateRegisterSlabHistoryRepository;
|
||||||
import org.apache.fineract.portfolio.loanproduct.serialization.LoanRateRegisterDataValidator;
|
import org.apache.fineract.portfolio.loanproduct.serialization.LoanRateRegisterDataValidator;
|
||||||
@@ -45,6 +56,8 @@ public class LoanRateRegisterWritePlatformServiceImpl implements LoanRateRegiste
|
|||||||
private final LoanRateRegisterEffectiveRepository effectiveRepository;
|
private final LoanRateRegisterEffectiveRepository effectiveRepository;
|
||||||
private final LoanRateRegisterSlabHistoryRepository slabRepository;
|
private final LoanRateRegisterSlabHistoryRepository slabRepository;
|
||||||
private final LoanRateRegisterCibilSlabRepository cibilRepository;
|
private final LoanRateRegisterCibilSlabRepository cibilRepository;
|
||||||
|
private final LoanRateRegisterProductRepository productRepository;
|
||||||
|
private final GLAccountRepositoryWrapper glAccountRepositoryWrapper;
|
||||||
private final Gson gson = new Gson();
|
private final Gson gson = new Gson();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,6 +69,10 @@ public class LoanRateRegisterWritePlatformServiceImpl implements LoanRateRegiste
|
|||||||
final LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
final LocalDate today = LocalDate.now(DateUtils.getDateTimeZoneOfTenant());
|
||||||
final LocalDate effectiveFrom = parseDateOrToday(req.getEffectiveFromDate(), today);
|
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).
|
// 1) Deactivate the currently-active version (if any).
|
||||||
final List<LoanRateRegisterEffective> active = this.effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc();
|
final List<LoanRateRegisterEffective> active = this.effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc();
|
||||||
for (final LoanRateRegisterEffective prior : active) {
|
for (final LoanRateRegisterEffective prior : active) {
|
||||||
@@ -65,17 +82,20 @@ public class LoanRateRegisterWritePlatformServiceImpl implements LoanRateRegiste
|
|||||||
this.effectiveRepository.saveAll(active);
|
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,
|
final LoanRateRegisterEffective fresh = LoanRateRegisterEffective.create(req.getName(), effectiveFrom,
|
||||||
req.getCurrencyCode(), req.getFallbackNoCibilSlab(), req.getFallbackBelowMin(), req.getNote(), createdById);
|
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();
|
final Long versionId = saved.getId();
|
||||||
|
|
||||||
// 3) Insert all chart cells for this version.
|
// 3) Insert all chart cells for this version.
|
||||||
final List<LoanRateRegisterSlabHistory> cells = new ArrayList<>(req.getSlabs().size());
|
final List<LoanRateRegisterSlabHistory> cells = new ArrayList<>(req.getSlabs().size());
|
||||||
for (final SlabRequest s : req.getSlabs()) {
|
for (final SlabRequest s : req.getSlabs()) {
|
||||||
cells.add(LoanRateRegisterSlabHistory.create(versionId, s.getProductSrNo(), s.getProductName(), s.getSlabOrder(),
|
final Long productId = productsBySrNo.get(s.getProductSrNo()).getId();
|
||||||
s.getSlabLabel(), s.getAmountUpto(), s.getRateValue(), s.getRateFormula(),
|
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())));
|
Boolean.TRUE.equals(s.getIsFormula()), Boolean.TRUE.equals(s.getIsNa())));
|
||||||
}
|
}
|
||||||
this.slabRepository.saveAll(cells);
|
this.slabRepository.saveAll(cells);
|
||||||
@@ -94,6 +114,83 @@ public class LoanRateRegisterWritePlatformServiceImpl implements LoanRateRegiste
|
|||||||
.build();
|
.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) {
|
private static LocalDate parseDateOrToday(final String iso, final LocalDate today) {
|
||||||
if (iso == null || iso.isBlank()) {
|
if (iso == null || iso.isBlank()) {
|
||||||
return today;
|
return today;
|
||||||
|
|||||||
@@ -619,4 +619,6 @@
|
|||||||
<include file="parts/0572_add_loan_product_advance_ext_fields.xml" relativeToChangelogFile="true"/>
|
<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/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/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"/>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
|
|||||||
+46
-12
@@ -28,29 +28,63 @@
|
|||||||
Enable maker-checker on the Loan Interest Rate Register. Mirrors 0470 for the FD side.
|
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
|
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
|
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">
|
<changeSet id="0581a_loan_rate_register_maker_checker_permissions" author="cbsnexora" runOnChange="false">
|
||||||
<validCheckSum>ANY</validCheckSum>
|
<validCheckSum>ANY</validCheckSum>
|
||||||
<sql>
|
<sql>
|
||||||
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
-- Rename any previously-seeded CBS-prefixed rows (safe no-op if a fresh DB).
|
||||||
SELECT 'portfolio', 'CREATE_CBSLOANBASEINTERESTRATE', 'CBSLOANBASEINTERESTRATE', 'CREATE', true
|
UPDATE m_permission
|
||||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_CBSLOANBASEINTERESTRATE');
|
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)
|
INSERT INTO m_permission (grouping, code, entity_name, action_name, can_maker_checker)
|
||||||
SELECT 'portfolio', 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER', 'CBSLOANBASEINTERESTRATE', 'CREATE_CHECKER', false
|
SELECT 'portfolio', 'CREATE_LOANBASEINTERESTRATE', 'LOANBASEINTERESTRATE', 'CREATE', true
|
||||||
WHERE NOT EXISTS (SELECT 1 FROM m_permission WHERE code = 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER');
|
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
|
UPDATE m_permission
|
||||||
SET can_maker_checker = TRUE
|
SET can_maker_checker = TRUE
|
||||||
WHERE entity_name = 'CBSLOANBASEINTERESTRATE'
|
WHERE entity_name = 'LOANBASEINTERESTRATE'
|
||||||
AND action_name = 'CREATE';
|
AND action_name = 'CREATE';
|
||||||
|
|
||||||
UPDATE m_permission
|
UPDATE m_permission
|
||||||
SET action_name = 'CREATE_CHECKER'
|
SET action_name = 'CREATE_CHECKER'
|
||||||
WHERE entity_name = 'CBSLOANBASEINTERESTRATE'
|
WHERE entity_name = 'LOANBASEINTERESTRATE'
|
||||||
AND code = 'CREATE_CBSLOANBASEINTERESTRATE_CHECKER';
|
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>
|
</sql>
|
||||||
</changeSet>
|
</changeSet>
|
||||||
|
|
||||||
@@ -63,7 +97,7 @@
|
|||||||
FROM m_role r
|
FROM m_role r
|
||||||
CROSS JOIN m_permission p
|
CROSS JOIN m_permission p
|
||||||
WHERE r.name = 'Super user'
|
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 (
|
AND NOT EXISTS (
|
||||||
SELECT 1 FROM m_role_permission rp
|
SELECT 1 FROM m_role_permission rp
|
||||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
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
|
JOIN m_appuser_role ur ON ur.appuser_id = u.id
|
||||||
CROSS JOIN m_permission p
|
CROSS JOIN m_permission p
|
||||||
WHERE u.username = 'bankadmin'
|
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 (
|
AND NOT EXISTS (
|
||||||
SELECT 1 FROM m_role_permission rp
|
SELECT 1 FROM m_role_permission rp
|
||||||
WHERE rp.role_id = ur.role_id AND rp.permission_id = p.id
|
WHERE rp.role_id = ur.role_id AND rp.permission_id = p.id
|
||||||
@@ -88,7 +122,7 @@
|
|||||||
FROM m_role r
|
FROM m_role r
|
||||||
CROSS JOIN m_permission p
|
CROSS JOIN m_permission p
|
||||||
WHERE LOWER(r.name) = 'clerk'
|
WHERE LOWER(r.name) = 'clerk'
|
||||||
AND p.code = 'CREATE_CBSLOANBASEINTERESTRATE'
|
AND p.code = 'CREATE_LOANBASEINTERESTRATE'
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT 1 FROM m_role_permission rp
|
SELECT 1 FROM m_role_permission rp
|
||||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
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>
|
||||||
Reference in New Issue
Block a user