This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 696be57c7ca863568fca66ac9a72829a111ac516 Author: adam.magyari <[email protected]> AuthorDate: Wed Dec 11 08:56:15 2024 +0100 FINERACT-1806: fix ProductToGLAccountMapping --- .../domain/ProductToGLAccountMapping.java | 10 +- .../ProductToGLAccountMappingRepository.java | 16 +- .../service/ProductToGLAccountMappingHelper.java | 10 +- ...tToGLAccountMappingReadPlatformServiceImpl.java | 232 ++++++--------------- .../api/LoanProductsApiResourceSwagger.java | 51 ++++- .../db/changelog/tenant/changelog-tenant.xml | 1 + ...60_add_acc_product_mapping_product_id_index.xml | 33 +++ .../LoanProductChargeOffReasonMappingsTest.java | 20 +- 8 files changed, 179 insertions(+), 194 deletions(-) diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java index 43680cb5b..5e953ff27 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java @@ -29,6 +29,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; import org.apache.fineract.accounting.glaccount.domain.GLAccount; +import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.paymenttype.domain.PaymentType; @@ -63,13 +64,14 @@ public class ProductToGLAccountMapping extends AbstractPersistableCustom<Long> { @Column(name = "financial_account_type", nullable = true) private int financialAccountType; - @Column(name = "charge_off_reason_id", nullable = true) - private Long chargeOffReasonId; + @ManyToOne + @JoinColumn(name = "charge_off_reason_id", nullable = true) + private CodeValue chargeOffReason; public static ProductToGLAccountMapping createNew(final GLAccount glAccount, final Long productId, final int productType, - final int financialAccountType, final Long chargeOffReasonId) { + final int financialAccountType, final CodeValue chargeOffReasonId) { return new ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId).setProductType(productType) - .setFinancialAccountType(financialAccountType).setChargeOffReasonId(chargeOffReasonId); + .setFinancialAccountType(financialAccountType).setChargeOffReason(chargeOffReasonId); } } diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java index acd32ad33..0dd2b6fb8 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java @@ -35,7 +35,7 @@ public interface ProductToGLAccountMappingRepository @Param("productType") int productType, @Param("financialAccountType") int financialAccountType, @Param("chargeId") Long ChargeId); - @Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL and mapping.chargeOffReasonId is NULL") + @Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.financialAccountType=:financialAccountType and mapping.paymentType is NULL and mapping.charge is NULL and mapping.chargeOffReason is NULL") ProductToGLAccountMapping findCoreProductToFinAccountMapping(@Param("productId") Long productId, @Param("productType") int productType, @Param("financialAccountType") int financialAccountType); @@ -62,10 +62,20 @@ public interface ProductToGLAccountMappingRepository List<ProductToGLAccountMapping> findByProductIdAndProductType(Long productId, int productType); - @Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.chargeOffReasonId is not NULL") + @Query("select mapping from ProductToGLAccountMapping mapping where mapping.productId =:productId and mapping.productType =:productType and mapping.chargeOffReason is not NULL") List<ProductToGLAccountMapping> findAllChargesOffReasonsMappings(@Param("productId") Long productId, @Param("productType") int productType); - @Query("select mapping from ProductToGLAccountMapping mapping where mapping.chargeOffReasonId =:chargeOffReasonId") + @Query("select mapping from ProductToGLAccountMapping mapping where mapping.chargeOffReason.id =:chargeOffReasonId") ProductToGLAccountMapping findChargesOffReasonMappingById(@Param("chargeOffReasonId") Integer chargeOffReasonId); + + List<ProductToGLAccountMapping> findAllByProductIdAndProductTypeAndPaymentTypeIsNullAndChargeIsNull(Long productId, + Integer productType); + + List<ProductToGLAccountMapping> findAllByProductIdAndProductTypeAndChargeOffReasonIdIsNotNull(Long productId, Integer productType); + + List<ProductToGLAccountMapping> findAllByProductIdAndProductTypeAndPaymentTypeIsNotNull(Long productId, Integer productType); + + List<ProductToGLAccountMapping> findAllByProductIdAndProductTypeAndCharge_PenaltyAndCharge_IdIsNotNull(Long productId, + Integer productType, boolean isPenalty); } diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java index 5c01b1670..34b9e06d7 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java @@ -414,7 +414,7 @@ public class ProductToGLAccountMappingHelper { this.accountMappingRepository.deleteAllInBatch(existingChargeOffReasonToGLAccountMappings); } else { for (final ProductToGLAccountMapping existingChargeOffReasonToGLAccountMapping : existingChargeOffReasonToGLAccountMappings) { - final Long currentChargeOffReasonId = existingChargeOffReasonToGLAccountMapping.getChargeOffReasonId(); + final Long currentChargeOffReasonId = existingChargeOffReasonToGLAccountMapping.getChargeOffReason().getId(); if (currentChargeOffReasonId != null) { existingChargeOffReasons.add(currentChargeOffReasonId); // update existing mappings (if required) @@ -496,12 +496,14 @@ public class ProductToGLAccountMappingHelper { final boolean reasonMappingExists = this.accountMappingRepository .findAllChargesOffReasonsMappings(productId, portfolioProductType.getValue()).stream() - .anyMatch(mapping -> mapping.getChargeOffReasonId().equals(reasonId)); + .anyMatch(mapping -> mapping.getChargeOffReason().getId().equals(reasonId)); - if (glAccount.isPresent() && !reasonMappingExists) { + final Optional<CodeValue> codeValueOptional = codeValueRepository.findById(reasonId); + + if (glAccount.isPresent() && !reasonMappingExists && codeValueOptional.isPresent()) { final ProductToGLAccountMapping accountMapping = new ProductToGLAccountMapping().setGlAccount(glAccount.get()) .setProductId(productId).setProductType(portfolioProductType.getValue()) - .setFinancialAccountType(CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue()).setChargeOffReasonId(reasonId); + .setFinancialAccountType(CashAccountsForLoan.CHARGE_OFF_EXPENSE.getValue()).setChargeOffReason(codeValueOptional.get()); this.accountMappingRepository.saveAndFlush(accountMapping); } diff --git a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java index 1803d12f1..fd3cbfd85 100644 --- a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java +++ b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java @@ -18,8 +18,6 @@ */ package org.apache.fineract.accounting.producttoaccountmapping.service; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -40,13 +38,13 @@ import org.apache.fineract.accounting.glaccount.data.GLAccountData; import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReasonToGLAccountMapper; import org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper; import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper; +import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping; +import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository; import org.apache.fineract.infrastructure.codes.data.CodeValueData; -import org.apache.fineract.infrastructure.core.domain.JdbcSupport; import org.apache.fineract.portfolio.PortfolioProductType; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Service; @Slf4j @@ -56,86 +54,24 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product private final JdbcTemplate jdbcTemplate; - private static final class ProductToGLAccountMappingMapper implements RowMapper<Map<String, Object>> { - - public String schema() { - return " mapping.id as id, mapping.gl_account_id as glAccountId,glaccount.name as name,glaccount.gl_code as code," - + " mapping.product_id as productId, mapping.product_type as productType,mapping.financial_account_type as financialAccountType, " - + " mapping.payment_type as paymentTypeId,pt.value as paymentTypeValue, mapping.charge_id as chargeId, mapping.charge_off_reason_id as chargeOffReasonId, charge.is_penalty as penalty, " - + " charge.name as chargeName, codeValue.code_value as codeValueName, codeValue.code_description as codeDescription, codeValue.order_position as orderPosition, codeValue.is_active as isActive, codeValue.is_mandatory as isMandatory " - + " from acc_product_mapping mapping left join m_charge charge on mapping.charge_id=charge.id " - + " left join acc_gl_account as glaccount on mapping.gl_account_id = glaccount.id " - + " left join m_code_value as codeValue on mapping.charge_off_reason_id = codeValue.id " - + " left join m_payment_type pt on mapping.payment_type=pt.id" + " where mapping.product_type= ? "; - } - - @Override - public Map<String, Object> mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final Long glAccountId = rs.getLong("glAccountId"); - final Long productId = rs.getLong("productId"); - final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentTypeId"); - final Long chargeId = rs.getLong("chargeId"); - final Integer productType = rs.getInt("productType"); - final String paymentTypeValue = rs.getString("paymentTypeValue"); - final Integer financialAccountType = rs.getInt("financialAccountType"); - final String glAccountName = rs.getString("name"); - final String glCode = rs.getString("code"); - final String chargeName = rs.getString("chargeName"); - final Boolean penalty = rs.getBoolean("penalty"); - final Integer chargeOffReasonId = rs.getInt("chargeOffReasonId"); - final String codeValue = rs.getString("codeValueName"); - final String codeDescription = rs.getString("codeDescription"); - final Integer orderPosition = rs.getInt("orderPosition"); - final Boolean isActive = rs.getBoolean("isActive"); - final Boolean isMandatory = rs.getBoolean("isMandatory"); - - final Map<String, Object> loanProductToGLAccountMap = new LinkedHashMap<>(5); - loanProductToGLAccountMap.put("id", id); - loanProductToGLAccountMap.put("glAccountId", glAccountId); - loanProductToGLAccountMap.put("productId", productId); - loanProductToGLAccountMap.put("productType", productType); - loanProductToGLAccountMap.put("financialAccountType", financialAccountType); - loanProductToGLAccountMap.put("paymentTypeId", paymentTypeId); - loanProductToGLAccountMap.put("paymentTypeValue", paymentTypeValue); - loanProductToGLAccountMap.put("chargeId", chargeId); - loanProductToGLAccountMap.put("chargeName", chargeName); - loanProductToGLAccountMap.put("penalty", penalty); - loanProductToGLAccountMap.put("glAccountName", glAccountName); - loanProductToGLAccountMap.put("glCode", glCode); - loanProductToGLAccountMap.put("chargeOffReasonId", chargeOffReasonId); - loanProductToGLAccountMap.put("codeValue", codeValue); - loanProductToGLAccountMap.put("codeDescription", codeDescription); - loanProductToGLAccountMap.put("orderPosition", orderPosition); - loanProductToGLAccountMap.put("isActive", isActive); - loanProductToGLAccountMap.put("isMandatory", isMandatory); - return loanProductToGLAccountMap; - } - } + private final ProductToGLAccountMappingRepository productToGLAccountMappingRepository; @Override public Map<String, Object> fetchAccountMappingDetailsForLoanProduct(final Long loanProductId, final Integer accountingType) { final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8); - final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); - final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null"; - - final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR - new Object[] { PortfolioProductType.LOAN.getValue(), loanProductId }); + final List<ProductToGLAccountMapping> mappings = productToGLAccountMappingRepository + .findAllByProductIdAndProductTypeAndPaymentTypeIsNullAndChargeIsNull(loanProductId, PortfolioProductType.LOAN.getValue()); if (AccountingValidations.isCashBasedAccounting(accountingType)) { - for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) { + for (final ProductToGLAccountMapping mapping : mappings) { - final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType"); - final CashAccountsForLoan glAccountForLoan = CashAccountsForLoan.fromInt(financialAccountType); + final CashAccountsForLoan glAccountForLoan = CashAccountsForLoan.fromInt(mapping.getFinancialAccountType()); - final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId"); - final String glAccountName = (String) productToGLAccountMap.get("glAccountName"); - final String glCode = (String) productToGLAccountMap.get("glCode"); - final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); + final GLAccountData gLAccountData = new GLAccountData().setId(mapping.getGlAccount().getId()) + .setName(mapping.getGlAccount().getName()).setGlCode(mapping.getGlAccount().getGlCode()); if (glAccountForLoan.equals(CashAccountsForLoan.FUND_SOURCE)) { accountMappingDetails.put(LoanProductAccountingDataParams.FUND_SOURCE.getValue(), gLAccountData); @@ -181,14 +117,11 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product } else if (AccountingValidations.isAccrualBasedAccounting(accountingType) || AccountingValidations.isUpfrontAccrualAccounting(accountingType)) { - for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) { - final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType"); - final AccrualAccountsForLoan glAccountForLoan = AccrualAccountsForLoan.fromInt(financialAccountType); + for (ProductToGLAccountMapping mapping : mappings) { + final AccrualAccountsForLoan glAccountForLoan = AccrualAccountsForLoan.fromInt(mapping.getFinancialAccountType()); - final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId"); - final String glAccountName = (String) productToGLAccountMap.get("glAccountName"); - final String glCode = (String) productToGLAccountMap.get("glCode"); - final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); + final GLAccountData gLAccountData = new GLAccountData().setId(mapping.getGlAccount().getId()) + .setName(mapping.getGlAccount().getName()).setGlCode(mapping.getGlAccount().getGlCode()); if (glAccountForLoan.equals(AccrualAccountsForLoan.FUND_SOURCE)) { accountMappingDetails.put(LoanProductAccountingDataParams.FUND_SOURCE.getValue(), gLAccountData); @@ -244,18 +177,17 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product @Override public Map<String, Object> fetchAccountMappingDetailsForSavingsProduct(final Long savingsProductId, final Integer accountingType) { - final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); - final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null "; - final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR - new Object[] { PortfolioProductType.SAVING.getValue(), savingsProductId }); + final List<ProductToGLAccountMapping> mappings = productToGLAccountMappingRepository + .findAllByProductIdAndProductTypeAndPaymentTypeIsNullAndChargeIsNull(savingsProductId, + PortfolioProductType.SAVING.getValue()); Map<String, Object> accountMappingDetails = null; if (AccountingValidations.isCashBasedAccounting(accountingType)) { - accountMappingDetails = setCashSavingsProductToGLAccountMaps(listOfProductToGLAccountMaps); + accountMappingDetails = setCashSavingsProductToGLAccountMaps(mappings); } else if (AccountingValidations.isAccrualPeriodicBasedAccounting(accountingType)) { - accountMappingDetails = setAccrualPeriodicSavingsProductToGLAccountMaps(listOfProductToGLAccountMaps); + accountMappingDetails = setAccrualPeriodicSavingsProductToGLAccountMaps(mappings); } @@ -278,25 +210,18 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product */ private List<PaymentTypeToGLAccountMapper> fetchPaymentTypeToFundSourceMappings(final PortfolioProductType portfolioProductType, final Long loanProductId) { - final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); - final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is not null"; - - final List<Map<String, Object>> paymentTypeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, // NOSONAR - new Object[] { portfolioProductType.getValue(), loanProductId }); + final List<ProductToGLAccountMapping> mappings = productToGLAccountMappingRepository + .findAllByProductIdAndProductTypeAndPaymentTypeIsNotNull(loanProductId, portfolioProductType.getValue()); List<PaymentTypeToGLAccountMapper> paymentTypeToGLAccountMappers = null; - for (final Map<String, Object> productToGLAccountMap : paymentTypeToFundSourceMappingsList) { + for (final ProductToGLAccountMapping mapping : mappings) { if (paymentTypeToGLAccountMappers == null) { paymentTypeToGLAccountMappers = new ArrayList<>(); } - final Long paymentTypeId = (Long) productToGLAccountMap.get("paymentTypeId"); - final String paymentTypeValue = (String) productToGLAccountMap.get("paymentTypeValue"); - final PaymentTypeData paymentTypeData = PaymentTypeData.instance(paymentTypeId, paymentTypeValue); - final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId"); - final String glAccountName = (String) productToGLAccountMap.get("glAccountName"); - final String glCode = (String) productToGLAccountMap.get("glCode"); - - final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); + final PaymentTypeData paymentTypeData = PaymentTypeData.instance(mapping.getPaymentType().getId(), + mapping.getPaymentType().getName()); + final GLAccountData gLAccountData = new GLAccountData().setId(mapping.getGlAccount().getId()) + .setName(mapping.getGlAccount().getName()).setGlCode(mapping.getGlAccount().getGlCode()); final PaymentTypeToGLAccountMapper paymentTypeToGLAccountMapper = new PaymentTypeToGLAccountMapper() .setPaymentType(paymentTypeData).setFundSourceAccount(gLAccountData); @@ -327,29 +252,18 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product private List<ChargeToGLAccountMapper> fetchChargeToIncomeAccountMappings(final PortfolioProductType portfolioProductType, final Long loanProductId, final boolean penalty) { - final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); - String sql = "select " + rm.schema() + " and product_id = ? and mapping.charge_id is not null and charge.is_penalty="; - if (penalty) { - sql = sql + " true"; - } else { - sql = sql + " false"; - } - - final List<Map<String, Object>> chargeToFundSourceMappingsList = this.jdbcTemplate.query(sql, rm, // NOSONAR - new Object[] { portfolioProductType.getValue(), loanProductId }); + final List<ProductToGLAccountMapping> mappings = productToGLAccountMappingRepository + .findAllByProductIdAndProductTypeAndCharge_PenaltyAndCharge_IdIsNotNull(loanProductId, portfolioProductType.getValue(), + penalty); List<ChargeToGLAccountMapper> chargeToGLAccountMappers = null; - for (final Map<String, Object> chargeToIncomeAccountMap : chargeToFundSourceMappingsList) { + for (final ProductToGLAccountMapping mapping : mappings) { if (chargeToGLAccountMappers == null) { chargeToGLAccountMappers = new ArrayList<>(); } - final Long glAccountId = (Long) chargeToIncomeAccountMap.get("glAccountId"); - final String glAccountName = (String) chargeToIncomeAccountMap.get("glAccountName"); - final String glCode = (String) chargeToIncomeAccountMap.get("glCode"); - final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); - final Long chargeId = (Long) chargeToIncomeAccountMap.get("chargeId"); - final String chargeName = (String) chargeToIncomeAccountMap.get("chargeName"); - final Boolean penalty1 = (Boolean) chargeToIncomeAccountMap.get("penalty"); - final ChargeData chargeData = ChargeData.builder().id(chargeId).name(chargeName).penalty(penalty1).build(); + final GLAccountData gLAccountData = new GLAccountData().setId(mapping.getGlAccount().getId()) + .setName(mapping.getGlAccount().getName()).setGlCode(mapping.getGlAccount().getGlCode()); + final ChargeData chargeData = ChargeData.builder().id(mapping.getCharge().getId()).name(mapping.getCharge().getName()) + .penalty(mapping.getCharge().isPenalty()).build(); final ChargeToGLAccountMapper chargeToGLAccountMapper = new ChargeToGLAccountMapper().setCharge(chargeData) .setIncomeAccount(gLAccountData); chargeToGLAccountMappers.add(chargeToGLAccountMapper); @@ -359,28 +273,25 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product private List<ChargeOffReasonToGLAccountMapper> fetchChargeOffReasonMappings(final PortfolioProductType portfolioProductType, final Long loanProductId) { - final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); - String sql = "select " + rm.schema() + " and product_id = ? and mapping.charge_off_reason_id is not null"; - - final List<Map<String, Object>> chargeOffReasonMappingsList = this.jdbcTemplate.query(sql, rm, // NOSONAR - new Object[] { portfolioProductType.getValue(), loanProductId }); + final List<ProductToGLAccountMapping> mappings = productToGLAccountMappingRepository + .findAllByProductIdAndProductTypeAndChargeOffReasonIdIsNotNull(loanProductId, portfolioProductType.getValue()); List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToGLAccountMappers = null; - for (final Map<String, Object> chargeOffReasonMap : chargeOffReasonMappingsList) { + for (final ProductToGLAccountMapping mapping : mappings) { if (chargeOffReasonToGLAccountMappers == null) { chargeOffReasonToGLAccountMappers = new ArrayList<>(); } - final Long glAccountId = (Long) chargeOffReasonMap.get("glAccountId"); - final String glAccountName = (String) chargeOffReasonMap.get("glAccountName"); - final String glCode = (String) chargeOffReasonMap.get("glCode"); + final Long glAccountId = mapping.getGlAccount().getId(); + final String glAccountName = mapping.getGlAccount().getName(); + final String glCode = mapping.getGlAccount().getGlCode(); final GLAccountData chargeOffExpenseAccount = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); - final Integer chargeOffReasonId = (Integer) chargeOffReasonMap.get("chargeOffReasonId"); - final String codeValue = (String) chargeOffReasonMap.get("codeValue"); - final String codeDescription = (String) chargeOffReasonMap.get("codeDescription"); - final Integer orderPosition = (Integer) chargeOffReasonMap.get("orderPosition"); - final Boolean active = (Boolean) chargeOffReasonMap.get("isActive"); - final Boolean mandatory = (Boolean) chargeOffReasonMap.get("isMandatory"); - final CodeValueData chargeOffReasonsCodeValue = CodeValueData.builder().id(Long.valueOf(chargeOffReasonId)).name(codeValue) - .description(codeDescription).position(orderPosition).active(active).mandatory(mandatory).build(); + final Long chargeOffReasonId = mapping.getChargeOffReason().getId(); + final String codeValue = mapping.getChargeOffReason().getLabel(); + final String codeDescription = mapping.getChargeOffReason().getDescription(); + final Integer orderPosition = mapping.getChargeOffReason().getPosition(); + final boolean isActive = mapping.getChargeOffReason().isActive(); + final boolean isMandatory = mapping.getChargeOffReason().isMandatory(); + final CodeValueData chargeOffReasonsCodeValue = CodeValueData.builder().id(chargeOffReasonId).name(codeValue) + .description(codeDescription).position(orderPosition).active(isActive).mandatory(isMandatory).build(); final ChargeOffReasonToGLAccountMapper chargeOffReasonToGLAccountMapper = new ChargeOffReasonToGLAccountMapper() .setChargeOffReasonsCodeValue(chargeOffReasonsCodeValue).setChargeOffExpenseAccount(chargeOffExpenseAccount); @@ -394,21 +305,15 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8); - final ProductToGLAccountMappingMapper rm = new ProductToGLAccountMappingMapper(); - final String sql = "select " + rm.schema() + " and product_id = ? and payment_type is null and mapping.charge_id is null "; - - final List<Map<String, Object>> listOfProductToGLAccountMaps = this.jdbcTemplate.query(sql, rm, // NOSONAR - new Object[] { PortfolioProductType.SHARES.getValue(), productId }); + final List<ProductToGLAccountMapping> mappings = productToGLAccountMappingRepository + .findAllByProductIdAndProductTypeAndPaymentTypeIsNullAndChargeIsNull(productId, PortfolioProductType.SHARES.getValue()); if (AccountingRuleType.CASH_BASED.getValue().equals(accountingType)) { - for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) { - final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType"); - final CashAccountsForShares glAccountForShares = CashAccountsForShares.fromInt(financialAccountType); + for (final ProductToGLAccountMapping mapping : mappings) { + final CashAccountsForShares glAccountForShares = CashAccountsForShares.fromInt(mapping.getFinancialAccountType()); - final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId"); - final String glAccountName = (String) productToGLAccountMap.get("glAccountName"); - final String glCode = (String) productToGLAccountMap.get("glCode"); - final GLAccountData gLAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); + final GLAccountData gLAccountData = new GLAccountData().setId(mapping.getGlAccount().getId()) + .setName(mapping.getGlAccount().getName()).setGlCode(mapping.getGlAccount().getGlCode()); if (glAccountForShares.equals(CashAccountsForShares.SHARES_REFERENCE)) { accountMappingDetails.put(SharesProductAccountingParams.SHARES_REFERENCE.getValue(), gLAccountData); @@ -440,20 +345,16 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product return fetchChargeOffReasonMappings(PortfolioProductType.LOAN, loanProductId); } - private Map<String, Object> setAccrualPeriodicSavingsProductToGLAccountMaps( - final List<Map<String, Object>> listOfProductToGLAccountMaps) { + private Map<String, Object> setAccrualPeriodicSavingsProductToGLAccountMaps(final List<ProductToGLAccountMapping> mappings) { final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8); - for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) { + for (final ProductToGLAccountMapping mapping : mappings) { - final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType"); - AccrualAccountsForSavings glAccountForSavings = AccrualAccountsForSavings.fromInt(financialAccountType); + AccrualAccountsForSavings glAccountForSavings = AccrualAccountsForSavings.fromInt(mapping.getFinancialAccountType()); if (glAccountForSavings != null) { - final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId"); - final String glAccountName = (String) productToGLAccountMap.get("glAccountName"); - final String glCode = (String) productToGLAccountMap.get("glCode"); - final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); + final GLAccountData glAccountData = new GLAccountData().setId(mapping.getGlAccount().getId()) + .setName(mapping.getGlAccount().getName()).setGlCode(mapping.getGlAccount().getGlCode()); // Assets if (glAccountForSavings.equals(AccrualAccountsForSavings.SAVINGS_REFERENCE)) { @@ -487,26 +388,23 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(SavingProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), glAccountData); } } else { - log.error("Accounting mapping null {}", financialAccountType); + log.error("Accounting mapping null {}", mapping.getFinancialAccountType()); } } return accountMappingDetails; } - private Map<String, Object> setCashSavingsProductToGLAccountMaps(final List<Map<String, Object>> listOfProductToGLAccountMaps) { + private Map<String, Object> setCashSavingsProductToGLAccountMaps(final List<ProductToGLAccountMapping> mappings) { final Map<String, Object> accountMappingDetails = new LinkedHashMap<>(8); - for (final Map<String, Object> productToGLAccountMap : listOfProductToGLAccountMaps) { + for (final ProductToGLAccountMapping mapping : mappings) { - final Integer financialAccountType = (Integer) productToGLAccountMap.get("financialAccountType"); - CashAccountsForSavings glAccountForSavings = CashAccountsForSavings.fromInt(financialAccountType); + CashAccountsForSavings glAccountForSavings = CashAccountsForSavings.fromInt(mapping.getFinancialAccountType()); if (glAccountForSavings != null) { - final Long glAccountId = (Long) productToGLAccountMap.get("glAccountId"); - final String glAccountName = (String) productToGLAccountMap.get("glAccountName"); - final String glCode = (String) productToGLAccountMap.get("glCode"); - final GLAccountData glAccountData = new GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode); + final GLAccountData glAccountData = new GLAccountData().setId(mapping.getGlAccount().getId()) + .setName(mapping.getGlAccount().getName()).setGlCode(mapping.getGlAccount().getGlCode()); // Assets if (glAccountForSavings.equals(CashAccountsForSavings.SAVINGS_REFERENCE)) { @@ -534,7 +432,7 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(SavingProductAccountingDataParams.LOSSES_WRITTEN_OFF.getValue(), glAccountData); } } else { - log.error("Accounting mapping null {}", financialAccountType); + log.error("Accounting mapping null {}", mapping.getFinancialAccountType()); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java index bc13d8629..0558a7a56 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java @@ -248,7 +248,7 @@ final class LoanProductsApiResourceSwagger { public Long incomeFromGoodwillCreditPenaltyAccountId; public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings; public List<GetLoanProductsProductIdResponse.GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings; - public List<GetLoanProductsProductIdResponse.GetChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings; + public List<PostChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings; public List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings; // Multi Disburse @@ -325,6 +325,16 @@ final class LoanProductsApiResourceSwagger { public List<String> supportedInterestRefundTypes; @Schema(example = "REGULAR") public String chargeOffBehaviour; + + static final class PostChargeOffReasonsToExpenseMappings { + + private PostChargeOffReasonsToExpenseMappings() {} + + @Schema(example = "1") + public Long chargeOffReasonCodeValueId; + @Schema(example = "1") + public Long expenseGLAccountId; + } } @Schema(description = "PostLoanProductsResponse") @@ -1223,10 +1233,37 @@ final class LoanProductsApiResourceSwagger { private GetChargeOffReasonsToExpenseMappings() {} - @Schema(example = "1") - public Long chargeOffReasonCodeValueId; - @Schema(example = "12") - public Long expenseGLAccountId; + public GetCodeValueData chargeOffReasonCodeValue; + public GetGLAccountData chargeOffExpenseAccount; + + static final class GetCodeValueData { + + private GetCodeValueData() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "ChargeOffReasons") + public String name; + @Schema(example = "1") + public Integer position; + public String description; + @Schema(example = "true") + public Boolean active; + @Schema(example = "false") + public Boolean mandatory; + } + + static final class GetGLAccountData { + + private GetGLAccountData() {} + + @Schema(example = "1") + public Long id; + @Schema(example = "Written off") + public String name; + @Schema(example = "e4") + public String glCode; + } } static final class GetLoanFeeToIncomeAccountMappings { @@ -1339,7 +1376,7 @@ final class LoanProductsApiResourceSwagger { public GetLoanAccountingMappings accountingMappings; public Set<GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings; public Set<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings; - public Set<GetChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings; + public Set<GetChargeOffReasonsToExpenseMappings> chargeOffReasonToGLAccountMappings; @Schema(example = "false") public Boolean isRatesEnabled; @Schema(example = "true") @@ -1599,7 +1636,7 @@ final class LoanProductsApiResourceSwagger { public Long incomeFromChargeOffPenaltyAccountId; public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings; public List<GetLoanProductsProductIdResponse.GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings; - public List<GetLoanProductsProductIdResponse.GetChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings; + public List<PostLoanProductsRequest.PostChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings; public List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings; @Schema(example = "false") public Boolean enableAccrualActivityPosting; diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index b540449e6..da814c8ad 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -178,4 +178,5 @@ <include file="parts/0157_create_index_m_loan_transaction_relation.xml" relativeToChangelogFile="true" /> <include file="parts/0158_create_acc_gl_journal_entry_annual_summary.xml" relativeToChangelogFile="true" /> <include file="parts/0159_trial_balance_summary_with_asset_owner_year_end_summary.xml" relativeToChangelogFile="true" /> + <include file="parts/0160_add_acc_product_mapping_product_id_index.xml" relativeToChangelogFile="true" /> </databaseChangeLog> diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0160_add_acc_product_mapping_product_id_index.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0160_add_acc_product_mapping_product_id_index.xml new file mode 100644 index 000000000..86675c406 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0160_add_acc_product_mapping_product_id_index.xml @@ -0,0 +1,33 @@ +<?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 + https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"> + + <changeSet id="1" author="fineract"> + <createIndex tableName="acc_product_mapping" indexName="IND_acc_product_mapping_product_id"> + <column name="product_id"/> + </createIndex> + + </changeSet> +</databaseChangeLog> diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductChargeOffReasonMappingsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductChargeOffReasonMappingsTest.java index 95d8a735c..c87d674ad 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductChargeOffReasonMappingsTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductChargeOffReasonMappingsTest.java @@ -31,24 +31,22 @@ import java.util.List; import org.apache.fineract.client.models.AllowAttributeOverrides; import org.apache.fineract.client.models.ChargeData; import org.apache.fineract.client.models.ChargeToGLAccountMapper; -import org.apache.fineract.client.models.GetChargeOffReasonsToExpenseMappings; import org.apache.fineract.client.models.GetLoanFeeToIncomeAccountMappings; import org.apache.fineract.client.models.GetLoanPaymentChannelToFundSourceMappings; +import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.PostChargeOffReasonsToExpenseMappings; import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.integrationtests.common.BusinessStepHelper; import org.apache.fineract.integrationtests.common.Utils; -import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper; import org.apache.fineract.integrationtests.common.system.CodeHelper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -@ExtendWith(LoanTestLifecycleExtension.class) public class LoanProductChargeOffReasonMappingsTest extends BaseLoanIntegrationTest { private static final String CODE_VALUE_NAME = "ChargeOffReasons"; @@ -77,10 +75,14 @@ public class LoanProductChargeOffReasonMappingsTest extends BaseLoanIntegrationT final String creationBusinessDay = "15 January 2023"; runAt(creationBusinessDay, () -> { Integer chargeOffReasons = createChargeOffReason(); - Long localLoanProductId = loanTransactionHelper.createLoanProduct(loanProductsRequest(Long.valueOf(chargeOffReasons), 15L)) + Long localLoanProductId = loanTransactionHelper.createLoanProduct(loanProductsRequest(Long.valueOf(chargeOffReasons), 16L)) .getResourceId(); Assertions.assertNotNull(localLoanProductId); + + final GetLoanProductsProductIdResponse loanProduct = loanTransactionHelper.getLoanProduct(localLoanProductId.intValue()); + Assertions.assertNotNull(loanProduct.getChargeOffReasonToGLAccountMappings()); + Assertions.assertFalse(loanProduct.getChargeOffReasonToGLAccountMappings().isEmpty()); }); } @@ -89,8 +91,8 @@ public class LoanProductChargeOffReasonMappingsTest extends BaseLoanIntegrationT final String creationBusinessDay = "15 January 2023"; runAt(creationBusinessDay, () -> { Integer chargeOffReasons = createChargeOffReason(); - List<GetChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings = new ArrayList<>(); - GetChargeOffReasonsToExpenseMappings getChargeOffReasonsToExpenseMappings = new GetChargeOffReasonsToExpenseMappings(); + List<PostChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings = new ArrayList<>(); + PostChargeOffReasonsToExpenseMappings getChargeOffReasonsToExpenseMappings = new PostChargeOffReasonsToExpenseMappings(); getChargeOffReasonsToExpenseMappings.setChargeOffReasonCodeValueId(Long.valueOf(chargeOffReasons)); getChargeOffReasonsToExpenseMappings.setExpenseGLAccountId(15L); chargeOffReasonsToExpenseMappings.add(getChargeOffReasonsToExpenseMappings); @@ -139,8 +141,8 @@ public class LoanProductChargeOffReasonMappingsTest extends BaseLoanIntegrationT List<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings = new ArrayList<>(); List<GetLoanFeeToIncomeAccountMappings> feeToIncomeAccountMappings = new ArrayList<>(); - List<GetChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings = new ArrayList<>(); - GetChargeOffReasonsToExpenseMappings getChargeOffReasonsToExpenseMappings = new GetChargeOffReasonsToExpenseMappings(); + List<PostChargeOffReasonsToExpenseMappings> chargeOffReasonsToExpenseMappings = new ArrayList<>(); + PostChargeOffReasonsToExpenseMappings getChargeOffReasonsToExpenseMappings = new PostChargeOffReasonsToExpenseMappings(); getChargeOffReasonsToExpenseMappings.setChargeOffReasonCodeValueId(chargeOffReasonId); getChargeOffReasonsToExpenseMappings.setExpenseGLAccountId(glAccountId); chargeOffReasonsToExpenseMappings.add(getChargeOffReasonsToExpenseMappings);
