FINERACT-2311: Buy Down fee adjustment - Template API

This commit is contained in:
Jose Alberto Hernandez
2025-07-08 12:20:01 +02:00
committed by Adam Saghy
parent 095e0818e4
commit 8a5300ef23
8 changed files with 86 additions and 27 deletions
@@ -21,29 +21,29 @@ package org.apache.fineract.portfolio.loanaccount.repository;
import java.math.BigDecimal;
import java.util.List;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
public interface LoanBuyDownFeesBalanceRepository
@Repository
public interface LoanBuyDownFeeBalanceRepository
extends JpaRepository<LoanBuyDownFeeBalance, Long>, JpaSpecificationExecutor<LoanBuyDownFeeBalance> {
String FIND_BALANCE_REPAYMENT_SCHEDULE_DATA = "SELECT new org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData(lbfb.loanTransaction.id, lbfb.loan.id, lbfb.loanTransaction.dateOf, lbfb.loanTransaction.reversed, lbfb.amount, lbfb.unrecognizedAmount, lbfb.loanTransaction.feeChargesPortion) FROM LoanBuyDownFeeBalance lbfb ";
List<LoanBuyDownFeeBalance> findAllByLoanId(Long loanId);
LoanBuyDownFeeBalance findByLoanIdAndLoanTransactionId(Long loanId, Long transactionId);
@Query(FIND_BALANCE_REPAYMENT_SCHEDULE_DATA + " WHERE lbfb.loan.id = :loanId")
List<LoanTransactionRepaymentPeriodData> findRepaymentPeriodDataByLoanId(Long loanId);
@Query("SELECT lbfb FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId ORDER BY lbfb.date ASC")
List<LoanBuyDownFeeBalance> findRepaymentPeriodDataByLoanId(@Param("loanId") Long loanId);
@Query("SELECT SUM(lbfb.amount) FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId")
BigDecimal calculateBuydownFeeAmount(Long loanId);
@Query("SELECT COALESCE(SUM(lbfb.amount), 0) FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId")
BigDecimal calculateBuyDownFee(@Param("loanId") Long loanId);
@Query("SELECT SUM(lbfb.amountAdjustment) FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId")
BigDecimal calculateBuydownFeeAdjustment(Long loanId);
@Query("SELECT COALESCE(SUM(lbfb.amountAdjustment), 0) FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId")
BigDecimal calculateBuyDownFeeAdjustment(@Param("loanId") Long loanId);
@Query("SELECT lbfb FROM LoanBuyDownFeeBalance lbfb, LoanTransaction lt, LoanTransactionRelation ltr WHERE lt.loan.id = lbfb.loan.id AND ltr.fromTransaction.id =:transactionId AND ltr.toTransaction.id=lt.id AND lbfb.loanTransaction.id = lt.id")
LoanBuyDownFeeBalance findBalanceForAdjustment(Long transactionId);
@Query("SELECT lbfb FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId AND lbfb.amountAdjustment IS NULL ORDER BY lbfb.date DESC")
List<LoanBuyDownFeeBalance> findBalanceForAdjustment(@Param("loanId") Long loanId);
}
@@ -51,7 +51,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
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.note.service.NoteWritePlatformService;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
@@ -68,7 +68,7 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
private final LoanJournalEntryPoster loanJournalEntryPoster;
private final NoteWritePlatformService noteWritePlatformService;
private final ExternalIdFactory externalIdFactory;
private final LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository;
private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository;
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanBalanceService loanBalanceService;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
@@ -177,13 +177,13 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
LoanTransaction savedBuyDownFeeAdjustment = loanTransactionRepository.saveAndFlush(buyDownFeeAdjustment);
// Update buy down fee balance
LoanBuyDownFeeBalance buydownFeeBalance = loanBuyDownFeesBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
LoanBuyDownFeeBalance buydownFeeBalance = loanBuyDownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
buyDownFeeTransactionId);
if (buydownFeeBalance != null) {
buydownFeeBalance.setAmountAdjustment(MathUtil.nullToZero(buydownFeeBalance.getAmountAdjustment()).add(transactionAmount));
buydownFeeBalance
.setUnrecognizedAmount(MathUtil.negativeToZero(buydownFeeBalance.getUnrecognizedAmount().subtract(transactionAmount)));
loanBuyDownFeesBalanceRepository.save(buydownFeeBalance);
loanBuyDownFeeBalanceRepository.save(buydownFeeBalance);
}
// Update outstanding loan balances
@@ -236,6 +236,6 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
buyDownFeeBalance.setDate(buyDownFeeTransaction.getTransactionDate());
buyDownFeeBalance.setAmount(buyDownFeeTransaction.getAmount());
buyDownFeeBalance.setUnrecognizedAmount(buyDownFeeTransaction.getAmount());
loanBuyDownFeesBalanceRepository.saveAndFlush(buyDownFeeBalance);
loanBuyDownFeeBalanceRepository.saveAndFlush(buyDownFeeBalance);
}
}
@@ -58,7 +58,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingException;
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeesBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -71,7 +71,7 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
private final LoanTransactionValidator loanTransactionValidator;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
private final LoanBuyDownFeesBalanceRepository loanBuydownFeeBalanceRepository;
private final LoanBuyDownFeeBalanceRepository loanBuydownFeeBalanceRepository;
private final LoanTransactionRepository loanTransactionRepository;
@Override
@@ -24,7 +24,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
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.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
import org.apache.fineract.portfolio.loanaccount.service.CapitalizedIncomeBalanceService;
@@ -66,9 +66,9 @@ public class ProgressiveLoanAccountConfiguration {
public ProgressiveLoanTransactionValidator progressiveLoanTransactionValidator(FromJsonHelper fromApiJsonHelper,
LoanTransactionValidator loanTransactionValidator, LoanRepositoryWrapper loanRepositoryWrapper,
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
LoanBuyDownFeesBalanceRepository loanBuydownFeesBalanceRepository, LoanTransactionRepository loanTransactionRepository) {
LoanBuyDownFeeBalanceRepository loanBuydownFeeBalanceRepository, LoanTransactionRepository loanTransactionRepository) {
return new ProgressiveLoanTransactionValidatorImpl(fromApiJsonHelper, loanTransactionValidator, loanRepositoryWrapper,
loanCapitalizedIncomeBalanceRepository, loanBuydownFeesBalanceRepository, loanTransactionRepository);
loanCapitalizedIncomeBalanceRepository, loanBuydownFeeBalanceRepository, loanTransactionRepository);
}
@Bean
@@ -688,6 +688,9 @@ public class LoanTransactionsApiResource {
LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT, transactionId);
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_COMMAND)) {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId);
} else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
transactionData = this.loanReadPlatformService.retrieveLoanTransactionTemplate(resolvedLoanId,
LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT, transactionId);
} else {
throw new UnrecognizedQueryParamException("command", commandParam);
}
@@ -109,6 +109,7 @@ import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
@@ -133,6 +134,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanSc
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper;
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanForeclosureValidator;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
@@ -189,6 +191,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
private final LoanTransactionProcessingService loadTransactionProcessingService;
private final LoanBalanceService loanBalanceService;
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository;
@Override
public LoanAccountData retrieveOne(final Long loanId) {
@@ -503,6 +506,16 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
break;
case BUY_DOWN_FEE_ADJUSTMENT:
final LoanBuyDownFeeBalance loanBuyDownFeeBalance = loanBuyDownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
transactionId);
transactionAmount = (loanBuyDownFeeBalance == null) ? BigDecimal.ZERO
: loanBuyDownFeeBalance.getAmount().subtract(MathUtil.nullToZero(loanBuyDownFeeBalance.getAmountAdjustment()));
loanTransactionData = LoanTransactionData.loanTransactionDataForCreditTemplate(
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
paymentOptions, retriveLoanCurrencyData(loanId));
break;
default:
loanTransactionData = LoanTransactionData.templateOnTop(retrieveLoanTransactionTemplate(loanId),
LoanEnumerations.transactionType(transactionType));
@@ -88,7 +88,7 @@ import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionMapper;
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeesBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionValidator;
@@ -339,7 +339,8 @@ public class LoanAccountConfiguration {
LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService,
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService,
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository) {
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository) {
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
@@ -347,7 +348,7 @@ public class LoanAccountConfiguration {
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService,
loanCapitalizedIncomeBalanceRepository);
loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository);
}
@Bean
@@ -383,12 +384,12 @@ public class LoanAccountConfiguration {
LoanAssembler loanAssembler, LoanTransactionRepository loanTransactionRepository,
PaymentDetailWritePlatformService paymentDetailWritePlatformService, LoanJournalEntryPoster loanJournalEntryPoster,
NoteWritePlatformService noteWritePlatformService, ExternalIdFactory externalIdFactory,
LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository,
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanBalanceService loanBalanceService,
LoanLifecycleStateMachine loanLifecycleStateMachine, BusinessEventNotifierService businessEventNotifierService) {
return new BuyDownFeeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository,
paymentDetailWritePlatformService, loanJournalEntryPoster, noteWritePlatformService, externalIdFactory,
loanBuyDownFeesBalanceRepository, reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine,
loanBuyDownFeeBalanceRepository, reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine,
businessEventNotifierService);
}
@@ -43,6 +43,8 @@ public class LoanTransactionTest extends BaseLoanIntegrationTest {
private final String capitalizedIncomeCommand = "capitalizedIncome";
private final String capitalizedIncomeAdjustmentCommand = "capitalizedIncomeAdjustment";
private final String buyDownFeeCommand = "buyDownFee";
private final String buyDownFeeAdjustmentCommand = "buyDownFeeAdjustment";
@Test
public void testGetLoanTransactionsFiltering() {
@@ -166,4 +168,44 @@ public class LoanTransactionTest extends BaseLoanIntegrationTest {
});
}
@Test
public void testGetLoanTransactionTemplateForBuyDownFeeAdjustment() {
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive()
.enableBuyDownFee(true).buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)//
.buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)//
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.INTEREST)//
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue()) //
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue()) //
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE) //
.buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue())//
.incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue()));
final String loanExternalIdStr = UUID.randomUUID().toString();
runAt("20 December 2024", () -> {
final 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");
PostLoansLoanIdTransactionsResponse loanTransactionResponse = loanTransactionHelper.executeLoanTransaction(loanId,
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate("20 December 2024").locale("en")
.transactionAmount(150.0),
buyDownFeeCommand);
assertNotNull(loanTransactionResponse);
final Long transactionId = loanTransactionResponse.getResourceId();
assertNotNull(transactionId);
log.info("Loan Id {} with transaction id {}", loanId, transactionId);
final GetLoansLoanIdTransactionsTemplateResponse transactionTemplate = loanTransactionHelper.retrieveTransactionTemplate(loanId,
buyDownFeeAdjustmentCommand, null, null, null, transactionId);
assertNotNull(transactionTemplate);
assertEquals("loanTransactionType." + buyDownFeeAdjustmentCommand, transactionTemplate.getType().getCode());
assertEquals(transactionTemplate.getAmount(), 150);
});
}
}