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
The following commit(s) were added to refs/heads/develop by this push:
new 53108227ae FINERACT-2311: Add Buy Down Fees transaction support
53108227ae is described below
commit 53108227ae5e217e5dcbc33a50e7a569c1c5608e
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Thu Jun 19 21:14:17 2025 -0500
FINERACT-2311: Add Buy Down Fees transaction support
---
.../loanaccount/domain/LoanTransactionType.java | 4 +--
.../loanaccount/domain/LoanBuyDownFeeBalance.java | 1 +
.../api/LoanTransactionsApiResource.java | 3 ++-
...uyDownFeeAmortizationProcessingServiceImpl.java | 8 +++---
.../service/LoanReadPlatformServiceImpl.java | 31 +++++++++++++++++++---
.../starter/LoanAccountConfiguration.java | 4 +--
.../integrationtests/LoanTransactionTest.java | 29 ++++++++++++++++++++
.../common/ExternalEventConfigurationHelper.java | 18 ++++++-------
8 files changed, 76 insertions(+), 22 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index 41489897c6..d1a6ede9f1 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -70,10 +70,8 @@ public enum LoanTransactionType {
CAPITALIZED_INCOME(35, "loanTransactionType.capitalizedIncome"), //
CAPITALIZED_INCOME_AMORTIZATION(36,
"loanTransactionType.capitalizedIncomeAmortization"), //
CAPITALIZED_INCOME_ADJUSTMENT(37,
"loanTransactionType.capitalizedIncomeAdjustment"), //
- CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT(39,
"loanTransactionType.capitalizedIncomeAmortizationAdjustment"), //
- // Kind of Final Transactions
CONTRACT_TERMINATION(38, "loanTransactionType.contractTermination"), //
-
+ CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT(39,
"loanTransactionType.capitalizedIncomeAmortizationAdjustment"), //
BUY_DOWN_FEE(40, "loanTransactionType.buyDownFee"), //
BUY_DOWN_FEE_ADJUSTMENT(41, "loanTransactionType.buyDownFeeAdjustment"), //
BUY_DOWN_FEE_AMORTIZATION(42,
"loanTransactionType.buyDownFeeAmortization"), //
diff --git
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeBalance.java
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeBalance.java
index ed6ce482ef..679ee0ebc9 100644
---
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeBalance.java
+++
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuyDownFeeBalance.java
@@ -61,4 +61,5 @@ public class LoanBuyDownFeeBalance extends
AbstractAuditableWithUTCDateTimeCusto
@Column(name = "amount_adjustment", scale = 6, precision = 19)
private BigDecimal amountAdjustment;
+
}
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 366d1d022d..3d0ef8d180 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -689,7 +689,8 @@ public class LoanTransactionsApiResource {
transactionData =
this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT,
transactionId);
} else if (CommandParameterUtil.is(commandParam,
LoanApiConstants.BUY_DOWN_FEE_COMMAND)) {
- transactionData =
this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId);
+ transactionData =
this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
LoanTransactionType.BUY_DOWN_FEE,
+ transactionId);
} else if (CommandParameterUtil.is(commandParam,
LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
transactionData =
this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT,
transactionId);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingServiceImpl.java
index 979bb4c065..c186d42812 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingServiceImpl.java
@@ -33,7 +33,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
-import
org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeesBalanceRepository;
+import
org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
import
org.apache.fineract.portfolio.loanaccount.util.BuyDownFeeAmortizationUtil;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
@@ -44,7 +44,7 @@ import
org.springframework.transaction.annotation.Transactional;
public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements
LoanBuyDownFeeAmortizationProcessingService {
private final LoanTransactionRepository loanTransactionRepository;
- private final LoanBuyDownFeesBalanceRepository
loanBuyDownFeesBalanceRepository;
+ private final LoanBuyDownFeeBalanceRepository
loanBuyDownFeeBalanceRepository;
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanJournalEntryPoster journalEntryPoster;
private final ExternalIdFactory externalIdFactory;
@@ -55,7 +55,7 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl
implements LoanBuyD
final List<Long> existingTransactionIds =
loanTransactionRepository.findTransactionIdsByLoan(loan);
final List<Long> existingReversedTransactionIds =
loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
- List<LoanBuyDownFeeBalance> balances =
loanBuyDownFeesBalanceRepository.findAllByLoanId(loan.getId());
+ List<LoanBuyDownFeeBalance> balances =
loanBuyDownFeeBalanceRepository.findAllByLoanId(loan.getId());
LocalDate maturityDate = loan.getMaturityDate() != null ?
loan.getMaturityDate()
: getFinalBuyDownFeeAmortizationTransactionDate(loan);
@@ -75,7 +75,7 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl
implements LoanBuyD
.subtract(amortizationTillDate.getAmount()));
}
- loanBuyDownFeesBalanceRepository.saveAll(balances);
+ loanBuyDownFeeBalanceRepository.saveAll(balances);
BigDecimal totalAmortized =
loanTransactionRepository.getAmortizedAmountBuyDownFee(loan);
BigDecimal totalAmortizationAmount =
totalAmortization.getAmount().subtract(totalAmortized);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index a409e09912..95113011ca 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -484,16 +484,41 @@ public class LoanReadPlatformServiceImpl implements
LoanReadPlatformService, Loa
switch (transactionType) {
case CAPITALIZED_INCOME:
final Loan loan =
loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
+ BigDecimal capitalizedIncomeBalance = BigDecimal.ZERO;
+ BigDecimal buyDownFeeBalance = BigDecimal.ZERO;
if
(loan.getLoanProduct().getLoanProductRelatedDetail().isEnableIncomeCapitalization())
{
- final BigDecimal capitalizedIncomeBalance =
loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
+ capitalizedIncomeBalance =
loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO,
BigDecimal::add);
- transactionAmount =
loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()).subtract(capitalizedIncomeBalance);
}
+ // Calculate Buy Down Fee balance to subtract from Capitalized
Income formula
+ buyDownFeeBalance =
loanBuyDownFeeBalanceRepository.findAllByLoanId(loanId).stream().map(LoanBuyDownFeeBalance::getAmount)
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
+ transactionAmount =
loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()).subtract(capitalizedIncomeBalance)
+ .subtract(buyDownFeeBalance);
+ paymentOptions =
this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+ loanTransactionData =
LoanTransactionData.loanTransactionDataForCreditTemplate(
+ LoanEnumerations.transactionType(transactionType),
DateUtils.getBusinessLocalDate(), transactionAmount,
+ paymentOptions, retriveLoanCurrencyData(loanId));
+ break;
+ case BUY_DOWN_FEE:
+ final Loan buyDownLoan =
loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
+ BigDecimal buyDownFeeBalanceForCalc = BigDecimal.ZERO;
+ BigDecimal capitalizedIncomeBalanceForCalc = BigDecimal.ZERO;
+
+ // Calculate existing Buy Down Fee balance
+ buyDownFeeBalanceForCalc =
loanBuyDownFeeBalanceRepository.findAllByLoanId(loanId).stream()
+
.map(LoanBuyDownFeeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
+ // Calculate Capitalized Income balance to subtract from Buy
Down Fee formula
+ if
(buyDownLoan.getLoanProduct().getLoanProductRelatedDetail().isEnableIncomeCapitalization())
{
+ capitalizedIncomeBalanceForCalc =
loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
+
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO,
BigDecimal::add);
+ }
+ transactionAmount =
buyDownLoan.getApprovedPrincipal().subtract(buyDownLoan.getDisbursedAmount())
+
.subtract(buyDownFeeBalanceForCalc).subtract(capitalizedIncomeBalanceForCalc);
paymentOptions =
this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
loanTransactionData =
LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType),
DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
-
break;
case CAPITALIZED_INCOME_ADJUSTMENT:
final LoanCapitalizedIncomeBalance
loanCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index a891a2aebf..60358ea5f7 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -567,10 +567,10 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(LoanBuyDownFeeAmortizationProcessingService.class)
public LoanBuyDownFeeAmortizationProcessingService
loanBuyDownFeeAmortizationProcessingService(
final LoanTransactionRepository loanTransactionRepository,
- final LoanBuyDownFeesBalanceRepository
loanBuyDownFeesBalanceRepository,
+ final LoanBuyDownFeeBalanceRepository
loanBuyDownFeeBalanceRepository,
final BusinessEventNotifierService businessEventNotifierService,
final LoanJournalEntryPoster journalEntryPoster,
final ExternalIdFactory externalIdFactory) {
- return new
LoanBuyDownFeeAmortizationProcessingServiceImpl(loanTransactionRepository,
loanBuyDownFeesBalanceRepository,
+ return new
LoanBuyDownFeeAmortizationProcessingServiceImpl(loanTransactionRepository,
loanBuyDownFeeBalanceRepository,
businessEventNotifierService, journalEntryPoster,
externalIdFactory);
}
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
index 16db6e0e90..62e515239a 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
@@ -208,4 +208,33 @@ public class LoanTransactionTest extends
BaseLoanIntegrationTest {
});
}
+ @Test
+ public void testGetLoanTransactionTemplateForBuyDownFee() {
+ final PostClientsResponse client =
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+ final PostLoanProductsResponse loanProductsResponse = loanProductHelper
+
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
+
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
+
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)
+
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())
+
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())
+
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE));
+
+ final String loanExternalIdStr = UUID.randomUUID().toString();
+
+ runAt("20 December 2024", () -> {
+ Long loanId = applyAndApproveProgressiveLoan(client.getClientId(),
loanProductsResponse.getResourceId(), "20 December 2024",
+ 430.0, 7.0, 6, (request) ->
request.externalId(loanExternalIdStr));
+
+ disburseLoan(loanId, BigDecimal.valueOf(230), "20 December 2024");
+
+ final GetLoansLoanIdTransactionsTemplateResponse
transactionTemplate = loanTransactionHelper.retrieveTransactionTemplate(loanId,
+ buyDownFeeCommand, null, null, null);
+
+ assertNotNull(transactionTemplate);
+ assertEquals("loanTransactionType." + buyDownFeeCommand,
transactionTemplate.getType().getCode());
+ assertEquals(transactionTemplate.getAmount(), 200);
+ assertThat(transactionTemplate.getPaymentTypeOptions().size() > 0);
+ });
+ }
}
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
index bc4c92951b..5970b58265 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
@@ -636,15 +636,15 @@ public class ExternalEventConfigurationHelper {
loanTransactionUndoContractTerminationBusinessEvent.put("enabled",
false);
defaults.add(loanTransactionUndoContractTerminationBusinessEvent);
- Map<String, Object> loanBuyDownFeeTransactionCreatedBusinessEvent =
new HashMap<>();
- loanBuyDownFeeTransactionCreatedBusinessEvent.put("type",
"LoanBuyDownFeeTransactionCreatedBusinessEvent");
- loanBuyDownFeeTransactionCreatedBusinessEvent.put("enabled", false);
- defaults.add(loanBuyDownFeeTransactionCreatedBusinessEvent);
-
- Map<String, Object>
loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent = new HashMap<>();
- loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.put("type",
"LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
- loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.put("enabled",
false);
- defaults.add(loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent);
+ Map<String, Object> loanTransactionBuyDownFeePostBusinessEvent = new
HashMap<>();
+ loanTransactionBuyDownFeePostBusinessEvent.put("type",
"LoanBuyDownFeeTransactionCreatedBusinessEvent");
+ loanTransactionBuyDownFeePostBusinessEvent.put("enabled", false);
+ defaults.add(loanTransactionBuyDownFeePostBusinessEvent);
+
+ Map<String, Object>
loanTransactionBuyDownFeeAdjustmentPostBusinessEvent = new HashMap<>();
+ loanTransactionBuyDownFeeAdjustmentPostBusinessEvent.put("type",
"LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
+ loanTransactionBuyDownFeeAdjustmentPostBusinessEvent.put("enabled",
false);
+ defaults.add(loanTransactionBuyDownFeeAdjustmentPostBusinessEvent);
Map<String, Object>
loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent = new HashMap<>();
loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent.put("type",
"LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent");