From d88f6a2f220e722a4d66b7c939069c0a6f9396b5 Mon Sep 17 00:00:00 2001 From: "adam.magyari" Date: Thu, 19 Jun 2025 11:07:39 +0200 Subject: [PATCH] FINERACT-2311: Loan product - Buy down fees accounting configuration --- .../ProductToGLAccountMappingHelper.java | 2 + ...AccountMappingReadPlatformServiceImpl.java | 4 ++ .../common/AccountingConstants.java | 15 ++++- .../LoanProductToGLAccountMappingHelper.java | 5 ++ .../api/LoanProductsApiResourceSwagger.java | 10 +++ ...ccountMappingWritePlatformServiceImpl.java | 6 ++ .../LoanProductDataValidator.java | 25 +++++-- .../BaseLoanIntegrationTest.java | 1 + .../integrationtests/LoanProductTest.java | 66 ++++++++++++++++--- 9 files changed, 118 insertions(+), 16 deletions(-) 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 bc92634c2..1b11ae5ec 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 @@ -102,6 +102,8 @@ public class ProductToGLAccountMappingHelper { optionalProductToGLAccountMappingEntries.add("incomeFromGoodwillCreditPenaltyAccountId"); optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.DEFERRED_INCOME_LIABILITY.getValue()); optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.INCOME_FROM_CAPITALIZATION.getValue()); + optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue()); + optionalProductToGLAccountMappingEntries.add(LoanProductAccountingParams.INCOME_FROM_BUY_DOWN.getValue()); if (optionalProductToGLAccountMappingEntries.contains(paramName)) { saveProductToAccountMapping(element, paramName, productId, accountTypeId, expectedAccountType, portfolioProductType); 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 4c24a19ac..3c250a0b6 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 @@ -171,6 +171,10 @@ public class ProductToGLAccountMappingReadPlatformServiceImpl implements Product accountMappingDetails.put(LoanProductAccountingDataParams.DEFERRED_INCOME_LIABILITY.getValue(), gLAccountData); } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_CAPITALIZATION)) { accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_CAPITALIZATION.getValue(), gLAccountData); + } else if (glAccountForLoan.equals(AccrualAccountsForLoan.BUY_DOWN_EXPENSE)) { + accountMappingDetails.put(LoanProductAccountingDataParams.BUY_DOWN_EXPENSE.getValue(), gLAccountData); + } else if (glAccountForLoan.equals(AccrualAccountsForLoan.INCOME_FROM_BUY_DOWN)) { + accountMappingDetails.put(LoanProductAccountingDataParams.INCOME_FROM_BUY_DOWN.getValue(), gLAccountData); } } diff --git a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java index 6c0c3e4b5..6016b5c29 100644 --- a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java @@ -111,7 +111,10 @@ public final class AccountingConstants { INCOME_FROM_GOODWILL_CREDIT_FEES(20), // INCOME_FROM_GOODWILL_CREDIT_PENALTY(21), // INCOME_FROM_CAPITALIZATION(22), // - DEFERRED_INCOME_LIABILITY(23); // + DEFERRED_INCOME_LIABILITY(23), // + BUY_DOWN_EXPENSE(24), // + INCOME_FROM_BUY_DOWN(25), // + ; private final Integer value; @@ -179,7 +182,10 @@ public final class AccountingConstants { EXPENSE_GL_ACCOUNT_ID("expenseAccountId"), // CHARGE_OFF_REASON_CODE_VALUE_ID("chargeOffReasonCodeValueId"), // DEFERRED_INCOME_LIABILITY("deferredIncomeLiabilityAccountId"), // - INCOME_FROM_CAPITALIZATION("incomeFromCapitalizationAccountId"); // + INCOME_FROM_CAPITALIZATION("incomeFromCapitalizationAccountId"), // + BUY_DOWN_EXPENSE("buyDownExpenseAccountId"), // + INCOME_FROM_BUY_DOWN("incomeFromBuyDownAccountId"), // + ; private final String value; @@ -223,7 +229,10 @@ public final class AccountingConstants { INCOME_FROM_GOODWILL_CREDIT_FEES("incomeFromGoodwillCreditFeesAccount"), // INCOME_FROM_GOODWILL_CREDIT_PENALTY("incomeFromGoodwillCreditPenaltyAccount"), // DEFERRED_INCOME_LIABILITY("deferredIncomeLiabilityAccount"), // - INCOME_FROM_CAPITALIZATION("incomeFromCapitalizationAccount"); // + INCOME_FROM_CAPITALIZATION("incomeFromCapitalizationAccount"), // + BUY_DOWN_EXPENSE("buyDownExpenseAccount"), // + INCOME_FROM_BUY_DOWN("incomeFromBuyDownAccount"), // + ; private final String value; diff --git a/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java b/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java index 15dd01719..69868e251 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java +++ b/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java @@ -363,6 +363,9 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_CAPITALIZATION.getValue(), loanProductId, AccrualAccountsForLoan.INCOME_FROM_CAPITALIZATION.getValue(), AccrualAccountsForLoan.INCOME_FROM_CAPITALIZATION.toString(), changes); + mergeLoanToIncomeAccountMappingChanges(element, LoanProductAccountingParams.INCOME_FROM_BUY_DOWN.getValue(), loanProductId, + AccrualAccountsForLoan.INCOME_FROM_BUY_DOWN.getValue(), AccrualAccountsForLoan.INCOME_FROM_BUY_DOWN.toString(), + changes); // expenses mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), loanProductId, @@ -376,6 +379,8 @@ public class LoanProductToGLAccountMappingHelper extends ProductToGLAccountMappi mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), loanProductId, AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue(), AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.toString(), changes); + mergeLoanToExpenseAccountMappingChanges(element, LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue(), loanProductId, + AccrualAccountsForLoan.BUY_DOWN_EXPENSE.getValue(), AccrualAccountsForLoan.BUY_DOWN_EXPENSE.toString(), changes); // liabilities mergeLoanToLiabilityAccountMappingChanges(element, LoanProductAccountingParams.OVERPAYMENT.getValue(), loanProductId, 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 9bdcc114d..a88860143 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 @@ -289,6 +289,10 @@ public final class LoanProductsApiResourceSwagger { public Long deferredIncomeLiabilityAccountId; @Schema(example = "37") public Long incomeFromCapitalizationAccountId; + @Schema(example = "27") + public Long buyDownExpenseAccountId; + @Schema(example = "38") + public Long incomeFromBuyDownAccountId; public List paymentChannelToFundSourceMappings; public List feeToIncomeAccountMappings; public List chargeOffReasonToExpenseAccountMappings; @@ -1268,6 +1272,8 @@ public final class LoanProductsApiResourceSwagger { public GetGlAccountMapping overpaymentLiabilityAccount; public GetGlAccountMapping deferredIncomeLiabilityAccount; public GetGlAccountMapping incomeFromCapitalizationAccount; + public GetGlAccountMapping buyDownExpenseAccount; + public GetGlAccountMapping incomeFromBuyDownAccount; } static final class GetLoanPaymentChannelToFundSourceMappings { @@ -1723,6 +1729,10 @@ public final class LoanProductsApiResourceSwagger { public Long deferredIncomeLiabilityAccountId; @Schema(example = "37") public Long incomeFromCapitalizationAccountId; + @Schema(example = "27") + public Long buyDownExpenseAccountId; + @Schema(example = "38") + public Long incomeFromBuyDownAccountId; public List paymentChannelToFundSourceMappings; public List feeToIncomeAccountMappings; public List chargeOffReasonToExpenseAccountMappings; diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java index 952c1dda2..224742d59 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java @@ -190,6 +190,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, LoanProductAccountingParams.INCOME_FROM_CAPITALIZATION.getValue(), loanProductId, AccrualAccountsForLoan.INCOME_FROM_CAPITALIZATION.getValue()); + this.loanProductToGLAccountMappingHelper.saveLoanToIncomeAccountMapping(element, + LoanProductAccountingParams.INCOME_FROM_BUY_DOWN.getValue(), loanProductId, + AccrualAccountsForLoan.INCOME_FROM_BUY_DOWN.getValue()); // expenses this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, @@ -204,6 +207,9 @@ public class ProductToGLAccountMappingWritePlatformServiceImpl implements Produc this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, LoanProductAccountingParams.CHARGE_OFF_FRAUD_EXPENSE.getValue(), loanProductId, AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE.getValue()); + this.loanProductToGLAccountMappingHelper.saveLoanToExpenseAccountMapping(element, + LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue(), loanProductId, + AccrualAccountsForLoan.BUY_DOWN_EXPENSE.getValue()); // liabilities this.loanProductToGLAccountMappingHelper.saveLoanToLiabilityAccountMapping(element, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index ef77da83b..e55b18b8d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -154,7 +154,8 @@ public final class LoanProductDataValidator { LoanProductAccountingParams.EXPENSE_GL_ACCOUNT_ID.getValue(), LoanProductAccountingParams.CHARGE_OFF_REASON_CODE_VALUE_ID.getValue(), LoanProductAccountingParams.INCOME_FROM_CAPITALIZATION.getValue(), - LoanProductAccountingParams.DEFERRED_INCOME_LIABILITY.getValue(), LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME, + LoanProductAccountingParams.DEFERRED_INCOME_LIABILITY.getValue(), LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue(), + LoanProductAccountingParams.INCOME_FROM_BUY_DOWN.getValue(), LoanProductConstants.USE_BORROWER_CYCLE_PARAMETER_NAME, LoanProductConstants.PRINCIPAL_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME, LoanProductConstants.INTEREST_RATE_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME, LoanProductConstants.NUMBER_OF_REPAYMENT_VARIATIONS_FOR_BORROWER_CYCLE_PARAMETER_NAME, LoanProductConstants.SHORT_NAME, @@ -892,7 +893,7 @@ public final class LoanProductDataValidator { validateIncomeCapitalization(transactionProcessingStrategyCode, element, baseDataValidator, accountingRuleType); - validateBuyDownFee(transactionProcessingStrategyCode, element, baseDataValidator); + validateBuyDownFee(transactionProcessingStrategyCode, element, baseDataValidator, accountingRuleType); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -1942,7 +1943,7 @@ public final class LoanProductDataValidator { validateIncomeCapitalization(transactionProcessingStrategyCode, element, baseDataValidator, accountingRuleType); - validateBuyDownFee(transactionProcessingStrategyCode, element, baseDataValidator); + validateBuyDownFee(transactionProcessingStrategyCode, element, baseDataValidator, accountingRuleType); throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -2754,7 +2755,8 @@ public final class LoanProductDataValidator { } } - private void validateBuyDownFee(String transactionProcessingStrategyCode, JsonElement element, DataValidatorBuilder baseDataValidator) { + private void validateBuyDownFee(String transactionProcessingStrategyCode, JsonElement element, DataValidatorBuilder baseDataValidator, + Integer accountingRuleType) { if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME, element)) { final String buyDownFeeCalculationType = this.fromApiJsonHelper .extractStringNamed(LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME, element); @@ -2798,6 +2800,21 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME).value(buyDownFeeIncomeType) .isOneOfEnumValues(LoanBuyDownFeeIncomeType.class) .cantBeBlankWhenParameterProvidedIs(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, true); + // Accounting + if (AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) { + final Long deferredIncomeLiabilityAccountId = this.fromApiJsonHelper + .extractLongNamed(LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue(), element); + baseDataValidator.reset().parameter(LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue()) + .value(deferredIncomeLiabilityAccountId).notNull() + .cantBeBlankWhenParameterProvidedIs(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, true) + .integerGreaterThanZero(); + final Long incomeFromDeferredIncomeAccountId = this.fromApiJsonHelper + .extractLongNamed(LoanProductAccountingParams.INCOME_FROM_BUY_DOWN.getValue(), element); + baseDataValidator.reset().parameter(LoanProductAccountingParams.INCOME_FROM_BUY_DOWN.getValue()) + .value(incomeFromDeferredIncomeAccountId).notNull() + .cantBeBlankWhenParameterProvidedIs(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, true) + .integerGreaterThanZero(); + } } } else if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, element)) { Boolean enableBuyDownFee = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index 2203aea4f..22c26834f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -162,6 +162,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest { protected final Account goodwillExpenseAccount = accountHelper.createExpenseAccount("goodwillExpenseAccount"); protected final Account goodwillIncomeAccount = accountHelper.createIncomeAccount("goodwillIncomeAccount"); protected final Account deferredIncomeLiabilityAccount = accountHelper.createLiabilityAccount("deferredIncomeLiabilityAccount"); + protected final Account buyDownExpenseAccount = accountHelper.createExpenseAccount("buyDownExpenseAccount"); protected final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); protected JournalEntryHelper journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec); protected ClientHelper clientHelper = new ClientHelper(requestSpec, responseSpec); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java index 8a2ec2e3d..44c83a9fe 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTest.java @@ -251,7 +251,9 @@ public class LoanProductTest extends BaseLoanIntegrationTest { final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() .enableBuyDownFee(true).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT) .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION) - .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)); + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE) + .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue()) + .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue())); final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper .retrieveLoanProductById(loanProductsResponse.getResourceId()); @@ -290,7 +292,9 @@ public class LoanProductTest extends BaseLoanIntegrationTest { final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() .enableBuyDownFee(false).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT) .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION) - .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)); + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE) + .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue()) + .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue())); final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper .retrieveLoanProductById(loanProductsResponse.getResourceId()); @@ -327,7 +331,9 @@ public class LoanProductTest extends BaseLoanIntegrationTest { final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive() .enableBuyDownFee(true).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT) .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION) - .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)); + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE) + .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue()) + .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue())); final GetLoanProductsProductIdResponse loanProductsProductIdResponse = loanProductHelper .retrieveLoanProductById(loanProductsResponse.getResourceId()); @@ -342,8 +348,16 @@ public class LoanProductTest extends BaseLoanIntegrationTest { Assertions.assertEquals(LoanBuyDownFeeIncomeType.FEE.getCode(), loanProductsProductIdResponse.getBuyDownFeeIncomeType().getCode()); - loanProductHelper.updateLoanProductById(loanProductsResponse.getResourceId(), new PutLoanProductsProductIdRequest() - .enableBuyDownFee(false).buyDownFeeIncomeType(PutLoanProductsProductIdRequest.BuyDownFeeIncomeTypeEnum.INTEREST)); + Assertions.assertNotNull(loanProductsProductIdResponse.getAccountingMappings()); + Assertions.assertEquals(buyDownExpenseAccount.getAccountID().longValue(), + loanProductsProductIdResponse.getAccountingMappings().getBuyDownExpenseAccount().getId()); + Assertions.assertEquals(feeIncomeAccount.getAccountID().longValue(), + loanProductsProductIdResponse.getAccountingMappings().getIncomeFromBuyDownAccount().getId()); + + loanProductHelper.updateLoanProductById(loanProductsResponse.getResourceId(), + new PutLoanProductsProductIdRequest().enableBuyDownFee(false) + .buyDownFeeIncomeType(PutLoanProductsProductIdRequest.BuyDownFeeIncomeTypeEnum.INTEREST) + .incomeFromBuyDownAccountId(interestIncomeAccount.getAccountID().longValue())); final GetLoanProductsProductIdResponse updatedLoanProductsProductIdResponse = loanProductHelper .retrieveLoanProductById(loanProductsResponse.getResourceId()); @@ -357,6 +371,12 @@ public class LoanProductTest extends BaseLoanIntegrationTest { Assertions.assertNotNull(updatedLoanProductsProductIdResponse.getBuyDownFeeIncomeType()); Assertions.assertEquals(LoanBuyDownFeeIncomeType.INTEREST.getCode(), updatedLoanProductsProductIdResponse.getBuyDownFeeIncomeType().getCode()); + + Assertions.assertNotNull(updatedLoanProductsProductIdResponse.getAccountingMappings()); + Assertions.assertEquals(buyDownExpenseAccount.getAccountID().longValue(), + updatedLoanProductsProductIdResponse.getAccountingMappings().getBuyDownExpenseAccount().getId()); + Assertions.assertEquals(interestIncomeAccount.getAccountID().longValue(), + updatedLoanProductsProductIdResponse.getAccountingMappings().getIncomeFromBuyDownAccount().getId()); } @Test @@ -365,7 +385,9 @@ public class LoanProductTest extends BaseLoanIntegrationTest { () -> loanProductHelper.createLoanProduct(createOnePeriod30DaysPeriodicAccrualProduct(7.0).enableBuyDownFee(true) .buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT) .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION) - .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE))); + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE) + .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue()) + .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue()))); } @Test @@ -373,7 +395,9 @@ public class LoanProductTest extends BaseLoanIntegrationTest { Assertions.assertThrows(RuntimeException.class, () -> loanProductHelper.createLoanProduct(create4IProgressive().enableBuyDownFee(true) .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION) - .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE))); + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE) + .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue()) + .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue()))); } @Test @@ -381,7 +405,9 @@ public class LoanProductTest extends BaseLoanIntegrationTest { Assertions.assertThrows(RuntimeException.class, () -> loanProductHelper.createLoanProduct(create4IProgressive().enableBuyDownFee(true) .buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT) - .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE))); + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE) + .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue()) + .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue()))); } @Test @@ -389,7 +415,29 @@ public class LoanProductTest extends BaseLoanIntegrationTest { Assertions.assertThrows(RuntimeException.class, () -> loanProductHelper.createLoanProduct(create4IProgressive().enableBuyDownFee(true) .buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT) - .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION))); + .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION) + .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue()) + .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue()))); + } + + @Test + public void testBuyDownFeeEnabledBuyDownExpenseNotProvided() { + Assertions.assertThrows(RuntimeException.class, + () -> loanProductHelper.createLoanProduct(create4IProgressive().enableBuyDownFee(true) + .buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT) + .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION) + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE) + .incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue()))); + } + + @Test + public void testBuyDownFeeEnabledIncomeFromBuyDownNotProvided() { + Assertions.assertThrows(RuntimeException.class, + () -> loanProductHelper.createLoanProduct(create4IProgressive().enableBuyDownFee(true) + .buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT) + .buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION) + .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE) + .buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue()))); } } }