FINERACT-2374: Advance Accounting rule for classification type

This commit is contained in:
Jose Alberto Hernandez
2025-09-30 12:27:45 +02:00
committed by Adam Saghy
parent 6d3e5fa150
commit cb70555485
31 changed files with 1023 additions and 66 deletions
@@ -149,6 +149,30 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi
updateChargeOffReasonToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN);
}
public void saveCapitalizedIncomeClassificationToIncomeAccountMappings(final JsonCommand command, final JsonElement element,
final Long productId, final Map<String, Object> changes) {
saveClassificationToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN,
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
}
public void updateCapitalizedIncomeClassificationToIncomeAccountMappings(final JsonCommand command, final JsonElement element,
final Long productId, final Map<String, Object> changes) {
updateClassificationToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN,
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
}
public void saveBuyDownFeeClassificationToIncomeAccountMappings(final JsonCommand command, final JsonElement element,
final Long productId, final Map<String, Object> changes) {
saveClassificationToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN,
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
}
public void updateBuyDownFeeClassificationToIncomeAccountMappings(final JsonCommand command, final JsonElement element,
final Long productId, final Map<String, Object> changes) {
updateClassificationToGLAccountMappings(command, element, productId, changes, PortfolioProductType.LOAN,
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
}
public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
// update both fee and penalty charges
@@ -25,6 +25,7 @@ import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.accounting.journalentry.data.AdvancedMappingtDTO;
@Getter
@Setter
@@ -47,5 +48,7 @@ public class AccountingBridgeDataDTO {
private boolean isWrittenOff;
private List<AccountingBridgeLoanTransactionDTO> newLoanTransactions = new ArrayList<>();
private boolean merchantBuyDownFee;
private List<AdvancedMappingtDTO> buydownFeeClassificationCodeValue;
private List<AdvancedMappingtDTO> capitalizedIncomeClassificationCodeValue;
}
@@ -61,4 +61,13 @@ public interface LoanAmortizationAllocationMappingRepository
WHERE laam.baseLoanTransactionId = :baseLoanTransactionId AND laam.loanId = :loanId
""")
BigDecimal calculateAlreadyAmortizedAmount(@Param("baseLoanTransactionId") Long baseLoanTransactionId, @Param("loanId") Long loanId);
@Query("""
SELECT laam FROM LoanAmortizationAllocationMapping laam
JOIN LoanTransaction at ON at.id = laam.baseLoanTransactionId
WHERE laam.amortizationLoanTransactionId = :amortizationLoanTransactionId
AND laam.loanId = :loanId
""")
List<LoanAmortizationAllocationMapping> fetchLoanTransactionAllocationByAmortizationLoanTransactionId(
@Param("amortizationLoanTransactionId") Long amortizationLoanTransactionId, @Param("loanId") Long loanId);
}
@@ -24,6 +24,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.portfolio.loanaccount.data.CumulativeIncomeFromIncomePosting;
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
@@ -473,4 +474,11 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
boolean existsNonReversedByLoanAndTypeAndDate(@Param("loan") Loan loan, @Param("type") LoanTransactionType type,
@Param("transactionDate") LocalDate transactionDate);
@Query("""
SELECT lt.classification
FROM LoanTransaction lt
WHERE lt.id = :transactionId
""")
CodeValue fetchClassificationCodeValueByTransactionId(@Param("transactionId") Long transactionId);
}
@@ -24,6 +24,7 @@ import java.time.LocalDate;
import java.util.List;
import java.util.Set;
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
import org.apache.fineract.infrastructure.codes.api.CodeValuesApiResourceSwagger.GetCodeValuesDataResponse;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
@@ -300,6 +301,8 @@ public final class LoanProductsApiResourceSwagger {
public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
public List<LoanProductChargeToGLAccountMapper> feeToIncomeAccountMappings;
public List<PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings;
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> buydownfeeClassificationToIncomeAccountMappings;
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> capitalizedIncomeClassificationToIncomeAccountMappings;
public List<LoanProductChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
// Multi Disburse
@@ -378,6 +381,16 @@ public final class LoanProductsApiResourceSwagger {
@Schema(example = "1")
public Long expenseAccountId;
}
static final class PostClassificationToIncomeAccountMappings {
private PostClassificationToIncomeAccountMappings() {}
@Schema(example = "1")
public Long classificationCodeValueId;
@Schema(example = "1")
public Long incomeAccountId;
}
}
@Schema(description = "PostLoanProductsResponse")
@@ -1168,6 +1181,8 @@ public final class LoanProductsApiResourceSwagger {
public List<StringEnumOptionData> buyDownFeeCalculationTypeOptions;
public List<StringEnumOptionData> buyDownFeeStrategyOptions;
public List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
public List<GetCodeValuesDataResponse> capitalizedIncomeClassificationOptions;
public List<GetCodeValuesDataResponse> buydownFeeClassificationOptions;
}
@Schema(description = "GetLoanProductsProductIdResponse")
@@ -1294,6 +1309,26 @@ public final class LoanProductsApiResourceSwagger {
public Long fundSourceAccountId;
}
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 GetClassificationToIncomeAccountMappings {
private GetClassificationToIncomeAccountMappings() {}
public GetCodeValuesDataResponse classificationCodeValue;
public GetGLAccountData incomeAccount;
}
static final class GetChargeOffReasonToExpenseAccountMappings {
private GetChargeOffReasonToExpenseAccountMappings() {}
@@ -1317,18 +1352,6 @@ public final class LoanProductsApiResourceSwagger {
@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 {
@@ -1516,6 +1539,11 @@ public final class LoanProductsApiResourceSwagger {
public List<StringEnumOptionData> buyDownFeeCalculationTypeOptions;
public List<StringEnumOptionData> buyDownFeeStrategyOptions;
public List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
public List<GetCodeValuesDataResponse> capitalizedIncomeClassificationOptions;
public List<GetCodeValuesDataResponse> buydownFeeClassificationOptions;
public List<GetClassificationToIncomeAccountMappings> buydownFeeClassificationToIncomeAccountMappings;
public List<GetClassificationToIncomeAccountMappings> capitalizedIncomeClassificationToIncomeAccountMappings;
}
@Schema(description = "PutLoanProductsProductIdRequest")
@@ -1746,6 +1774,8 @@ public final class LoanProductsApiResourceSwagger {
public List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings> paymentChannelToFundSourceMappings;
public List<LoanProductChargeToGLAccountMapper> feeToIncomeAccountMappings;
public List<PostLoanProductsRequest.PostChargeOffReasonToExpenseAccountMappings> chargeOffReasonToExpenseAccountMappings;
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> buydownfeeClassificationToIncomeAccountMappings;
public List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> capitalizedIncomeClassificationToIncomeAccountMappings;
public List<LoanProductChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
@Schema(example = "false")
public Boolean enableAccrualActivityPosting;
@@ -32,6 +32,7 @@ import org.apache.fineract.accounting.common.AccountingRuleType;
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.ClassificationToGLAccountData;
import org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
@@ -261,6 +262,11 @@ public class LoanProductData implements Serializable {
private List<StringEnumOptionData> buyDownFeeStrategyOptions;
private List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
private final List<CodeValueData> capitalizedIncomeClassificationOptions;
private final List<CodeValueData> buydownFeeClassificationOptions;
private List<ClassificationToGLAccountData> capitalizedIncomeClassificationToIncomeAccountMappings;
private List<ClassificationToGLAccountData> buydownFeeClassificationToIncomeAccountMappings;
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -822,12 +828,16 @@ public class LoanProductData implements Serializable {
final Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings,
final Collection<ChargeToGLAccountMapper> feeToGLAccountMappings,
final Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings,
final List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToGLAccountMappings) {
final List<ChargeOffReasonToGLAccountMapper> chargeOffReasonToGLAccountMappings,
final List<ClassificationToGLAccountData> capitalizedIncomeClassificationToIncomeAccountMappings,
final List<ClassificationToGLAccountData> buydownFeeClassificationToIncomeAccountMappings) {
productData.accountingMappings = accountingMappings;
productData.paymentChannelToFundSourceMappings = paymentChannelToFundSourceMappings;
productData.feeToIncomeAccountMappings = feeToGLAccountMappings;
productData.penaltyToIncomeAccountMappings = penaltyToGLAccountMappings;
productData.chargeOffReasonToExpenseAccountMappings = chargeOffReasonToGLAccountMappings;
productData.capitalizedIncomeClassificationToIncomeAccountMappings = capitalizedIncomeClassificationToIncomeAccountMappings;
productData.buydownFeeClassificationToIncomeAccountMappings = buydownFeeClassificationToIncomeAccountMappings;
return productData;
}
@@ -1036,6 +1046,10 @@ public class LoanProductData implements Serializable {
this.buyDownFeeCalculationTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeCalculationType.class);
this.buyDownFeeStrategyOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
this.buyDownFeeIncomeTypeOptions = ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
this.capitalizedIncomeClassificationOptions = null;
this.buydownFeeClassificationOptions = null;
this.capitalizedIncomeClassificationToIncomeAccountMappings = null;
this.buydownFeeClassificationToIncomeAccountMappings = null;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -1065,7 +1079,8 @@ public class LoanProductData implements Serializable {
final List<StringEnumOptionData> capitalizedIncomeStrategyOptions,
final List<StringEnumOptionData> capitalizedIncomeTypeOptions,
final List<StringEnumOptionData> buyDownFeeCalculationTypeOptions, final List<StringEnumOptionData> buyDownFeeStrategyOptions,
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions) {
final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions, final List<CodeValueData> capitalizedIncomeClassificationOptions,
final List<CodeValueData> buydownFeeClassificationOptions) {
this.id = productData.id;
this.name = productData.name;
@@ -1246,6 +1261,10 @@ public class LoanProductData implements Serializable {
this.buyDownFeeIncomeTypeOptions = buyDownFeeIncomeTypeOptions;
this.merchantBuyDownFee = productData.isMerchantBuyDownFee();
this.capitalizedIncomeClassificationOptions = capitalizedIncomeClassificationOptions;
this.buydownFeeClassificationOptions = buydownFeeClassificationOptions;
this.buydownFeeClassificationToIncomeAccountMappings = productData.buydownFeeClassificationToIncomeAccountMappings;
this.capitalizedIncomeClassificationToIncomeAccountMappings = productData.capitalizedIncomeClassificationToIncomeAccountMappings;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -18,30 +18,25 @@
*/
package org.apache.fineract.portfolio.loanproduct.handler;
import lombok.AllArgsConstructor;
import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@AllArgsConstructor
@CommandType(entity = "LOANPRODUCT", action = "CREATE")
public class CreateLoanProductCommandHandler implements NewCommandSourceHandler {
private final LoanProductWritePlatformService writePlatformService;
@Autowired
public CreateLoanProductCommandHandler(final LoanProductWritePlatformService writePlatformService) {
this.writePlatformService = writePlatformService;
}
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.createLoanProduct(command);
}
}