diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index bbae94c10..a3f2c14c4 100644 --- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -3841,6 +3841,15 @@ public class CommandWrapperBuilder { return this; } + public CommandWrapperBuilder buyDownFeeAdjustment(final Long loanId, final Long transactionId) { + this.actionName = "BUYDOWNFEEADJUSTMENT"; + this.entityName = "LOAN"; + this.entityId = transactionId; + this.loanId = loanId; + this.href = "/loans/" + loanId + "/transactions/" + transactionId; + return this; + } + public CommandWrapperBuilder applyContractTermination(final Long loanId) { this.actionName = "CONTRACT_TERMINATION"; this.entityName = "LOAN"; diff --git a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.java b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.java new file mode 100644 index 000000000..ee91f4b17 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.java @@ -0,0 +1,35 @@ +/** + * 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. + */ +package org.apache.fineract.infrastructure.event.business.domain.loan.transaction; + +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; + +public class LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent { + + private static final String TYPE = "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent"; + + public LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent(LoanTransaction value) { + super(value); + } + + @Override + public String getType() { + return TYPE; + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeTransactionCreatedBusinessEvent.java b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeTransactionCreatedBusinessEvent.java new file mode 100644 index 000000000..ae9c7497d --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeTransactionCreatedBusinessEvent.java @@ -0,0 +1,35 @@ +/** + * 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. + */ +package org.apache.fineract.infrastructure.event.business.domain.loan.transaction; + +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; + +public class LoanBuyDownFeeTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent { + + private static final String TYPE = "LoanBuyDownFeeTransactionCreatedBusinessEvent"; + + public LoanBuyDownFeeTransactionCreatedBusinessEvent(LoanTransaction value) { + super(value); + } + + @Override + public String getType() { + return TYPE; + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 6c59aa39b..9a1c48dd1 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -332,6 +332,12 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom, JpaSpecificationExecutor {} + extends JpaRepository, JpaSpecificationExecutor { + + 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 findAllByLoanId(Long loanId); + + LoanBuyDownFeeBalance findByLoanIdAndLoanTransactionId(Long loanId, Long transactionId); + + @Query(FIND_BALANCE_REPAYMENT_SCHEDULE_DATA + " WHERE lbfb.loan.id = :loanId") + List findRepaymentPeriodDataByLoanId(Long loanId); + + @Query("SELECT SUM(lbfb.amount) FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId") + BigDecimal calculateBuydownFeeAmount(Long loanId); + + @Query("SELECT SUM(lbfb.amountAdjustment) FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId") + BigDecimal calculateBuydownFeeAdjustment(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); +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java index 8ef5620ab..69180af13 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java @@ -26,4 +26,7 @@ public interface BuyDownFeePlatformService { @Transactional CommandProcessingResult makeLoanBuyDownFee(Long loanId, JsonCommand command); + + @Transactional + CommandProcessingResult buyDownFeeAdjustment(Long loanId, Long buyDownFeeTransactionId, JsonCommand command); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java index b54c18de2..902b0a46d 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -31,7 +32,13 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.core.service.MathUtil; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeTransactionCreatedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; @@ -39,7 +46,10 @@ import org.apache.fineract.portfolio.group.domain.Group; import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance; +import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; 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.note.service.NoteWritePlatformService; @@ -59,6 +69,10 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer private final NoteWritePlatformService noteWritePlatformService; private final ExternalIdFactory externalIdFactory; private final LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; + private final LoanBalanceService loanBalanceService; + private final LoanLifecycleStateMachine loanLifecycleStateMachine; + private final BusinessEventNotifierService businessEventNotifierService; @Transactional @Override @@ -92,7 +106,7 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer // Save transaction loanTransactionRepository.saveAndFlush(buyDownFeeTransaction); - // Create Buy Down Fee balances + // Create Buy Down Fee balance createBuyDownFeeBalance(buyDownFeeTransaction); // Update loan derived fields @@ -107,6 +121,10 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer // Post journal entries loanJournalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + // Notify business events + businessEventNotifierService.notifyPostBusinessEvent(new LoanBuyDownFeeTransactionCreatedBusinessEvent(buyDownFeeTransaction)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + return new CommandProcessingResultBuilder() // .withClientId(loan.getClientId()) // .withOfficeId(loan.getOfficeId()) // @@ -116,6 +134,90 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer .build(); } + @Override + @Transactional + public CommandProcessingResult buyDownFeeAdjustment(final Long loanId, final Long buyDownFeeTransactionId, final JsonCommand command) { + this.loanTransactionValidator.validateBuyDownFeeAdjustment(command, loanId, buyDownFeeTransactionId); + final Loan loan = this.loanAssembler.assembleFrom(loanId); + checkClientOrGroupActive(loan); + + final List existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan)); + final List existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan)); + final Map changes = new LinkedHashMap<>(); + + // Create payment details + final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); + + // Extract transaction details + final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate"); + final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount"); + final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, "externalId"); + + // Find and validate original buy down fee transaction + Optional originalBuyDownFee = loanTransactionRepository.findById(buyDownFeeTransactionId); + if (originalBuyDownFee.isEmpty() || !originalBuyDownFee.get().isBuyDownFee()) { + throw new IllegalArgumentException("Original transaction must be a valid Buy Down Fee transaction"); + } + + // Create buy down fee adjustment transaction + LoanTransaction buyDownFeeAdjustment = LoanTransaction.buyDownFeeAdjustment(loan, Money.of(loan.getCurrency(), transactionAmount), + paymentDetail, transactionDate, txnExternalId); + + // Link to original transaction + buyDownFeeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(buyDownFeeAdjustment, + originalBuyDownFee.get(), LoanTransactionRelationTypeEnum.ADJUSTMENT)); + + // Add transaction to loan + loan.addLoanTransaction(buyDownFeeAdjustment); + + // Recalculate loan transactions + recalculateLoanTransactions(loan, transactionDate, buyDownFeeAdjustment); + + // Save transaction + LoanTransaction savedBuyDownFeeAdjustment = loanTransactionRepository.saveAndFlush(buyDownFeeAdjustment); + + // Update buy down fee balance + LoanBuyDownFeeBalance buydownFeeBalance = loanBuyDownFeesBalanceRepository.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); + } + + // Update outstanding loan balances + loanBalanceService.updateLoanOutstandingBalances(loan); + + // Create a note if provided + final String noteText = command.stringValueOfParameterNamed("note"); + if (StringUtils.isNotBlank(noteText)) { + noteWritePlatformService.createLoanTransactionNote(savedBuyDownFeeAdjustment.getId(), noteText); + } + // Post journal entries + loanJournalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + + // Determine loan lifecycle transition + loanLifecycleStateMachine.determineAndTransition(loan, transactionDate); + + // Notify business events + businessEventNotifierService + .notifyPostBusinessEvent(new LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent(savedBuyDownFeeAdjustment)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + + return new CommandProcessingResultBuilder().withEntityId(savedBuyDownFeeAdjustment.getId()) + .withEntityExternalId(savedBuyDownFeeAdjustment.getExternalId()).withOfficeId(loan.getOfficeId()) + .withClientId(loan.getClientId()).withLoanId(loan.getId()).build(); + } + + private void recalculateLoanTransactions(Loan loan, LocalDate transactionDate, LoanTransaction transaction) { + if (loan.isInterestBearingAndInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transactionDate)) { + reprocessLoanTransactionsService.reprocessTransactions(loan); + } else { + reprocessLoanTransactionsService.processLatestTransaction(transaction, loan); + } + } + private void checkClientOrGroupActive(final Loan loan) { final Client client = loan.client(); if (client != null && client.isNotActive()) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidator.java index 825a6e85f..553c6adff 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidator.java @@ -30,4 +30,6 @@ public interface ProgressiveLoanTransactionValidator extends LoanTransactionVali void validateContractTerminationUndo(JsonCommand command, Long loanId); void validateBuyDownFee(JsonCommand command, Long loanId); + + void validateBuyDownFeeAdjustment(JsonCommand command, Long loanId, Long buyDownFeeTransactionId); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java index 73480a7a4..46bf83fdd 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java @@ -48,6 +48,7 @@ import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.portfolio.common.service.Validator; 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.LoanCapitalizedIncomeBalance; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; @@ -57,6 +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.LoanCapitalizedIncomeBalanceRepository; import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; @@ -69,6 +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 LoanTransactionRepository loanTransactionRepository; @Override @@ -320,6 +323,94 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT } } + @Override + public void validateBuyDownFeeAdjustment(JsonCommand command, Long loanId, Long buyDownFeeTransactionId) { + final String json = command.json(); + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final JsonElement element = this.fromApiJsonHelper.parse(json); + final Type typeOfMap = new TypeToken>() {}.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getBuyDownFeeAdjustmentParameters()); + + Validator.validateOrThrow("loan.buyDownFeeAdjustment", baseDataValidator -> { + final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true); + validateLoanClientIsActive(loan); + validateLoanGroupIsActive(loan); + + // Validate loan is progressive + if (!loan.isProgressiveSchedule()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.progressive.loan"); + } + + // Validate buy down fee is enabled + if (!loan.getLoanProductRelatedDetail().isEnableBuyDownFee()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("buy.down.fee.not.enabled"); + } + + // Validate loan is active, or closed or overpaid + final LoanStatus loanStatus = loan.getStatus(); + if (!loanStatus.isActive() && !loanStatus.isClosed() && !loanStatus.isOverpaid()) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.valid.loan.status"); + } + + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element); + baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull(); + + // Validate transaction date is not before disbursement date + if (transactionDate != null && loan.getDisbursementDate() != null && transactionDate.isBefore(loan.getDisbursementDate())) { + baseDataValidator.reset().parameter("transactionDate").failWithCode("before.disbursement.date", + "Transaction date cannot be before disbursement date"); + } + + // Validate transaction date is not in the future + if (transactionDate != null && transactionDate.isAfter(DateUtils.getBusinessLocalDate())) { + baseDataValidator.reset().parameter("transactionDate").failWithCode("cannot.be.in.the.future", + "Transaction date cannot be in the future"); + } + + final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element); + baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount(); + + Optional buyDownFeeTransactionOpt = loanTransactionRepository.findById(buyDownFeeTransactionId); + if (buyDownFeeTransactionOpt.isEmpty()) { + baseDataValidator.reset().parameter("buyDownFeeTransactionId").failWithCode("loan.transaction.not.found", + "Buy Down Fee transaction not found."); + } else { + // Validate that the transaction is actually a buy down fee transaction + if (!buyDownFeeTransactionOpt.get().isBuyDownFee()) { + baseDataValidator.reset().parameter("buyDownFeeTransactionId").failWithCode("not.buyDownFee.transaction", + "The specified transaction is not a Buy Down Fee transaction."); + } + // Validate not before buy down fee transaction + if (transactionDate != null && transactionDate.isBefore(buyDownFeeTransactionOpt.get().getTransactionDate())) { + baseDataValidator.reset().parameter("transactionDate").failWithCode("before.buyDownFee.transaction.date", + "Transaction date cannot be before buy down fee transaction date"); + + } + if (transactionAmount != null) { + LoanBuyDownFeeBalance buydownFeeBalance = loanBuydownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId, + buyDownFeeTransactionId); + if (buydownFeeBalance == null) { + baseDataValidator.reset().parameter("buyDownFeeTransactionId").failWithCode("buydown.fee.balance.not.found", + "Buy down fee balance not found for the specified transaction."); + } else if (MathUtil.isLessThan( + buydownFeeBalance.getAmount().subtract(MathUtil.nullToZero(buydownFeeBalance.getAmountAdjustment())), + transactionAmount)) { + baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).failWithCode( + "cannot.be.more.than.remaining.amount", + " Buy down fee adjustment amount cannot be more than remaining amount"); + } + } + } + + validatePaymentDetails(baseDataValidator, element); + validateNote(baseDataValidator, element); + validateExternalId(baseDataValidator, element); + }); + } + private void throwExceptionIfValidationWarningsExist(final List dataValidationErrors) { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", @@ -495,6 +586,11 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT return new HashSet<>(Arrays.asList("note", "reversalExternalId")); } + private Set getBuyDownFeeAdjustmentParameters() { + return new HashSet<>( + Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId")); + } + private BigDecimal getOverAppliedMax(final Loan loan) { final LoanProduct loanProduct = loan.getLoanProduct(); if ("percentage".equals(loanProduct.getOverAppliedCalculationType())) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java index 8eb7fbad2..6e6665ed6 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java @@ -24,6 +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.LoanCapitalizedIncomeBalanceRepository; import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator; import org.apache.fineract.portfolio.loanaccount.service.CapitalizedIncomeBalanceService; @@ -65,9 +66,9 @@ public class ProgressiveLoanAccountConfiguration { public ProgressiveLoanTransactionValidator progressiveLoanTransactionValidator(FromJsonHelper fromApiJsonHelper, LoanTransactionValidator loanTransactionValidator, LoanRepositoryWrapper loanRepositoryWrapper, LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository, - LoanTransactionRepository loanTransactionRepository) { + LoanBuyDownFeesBalanceRepository loanBuydownFeesBalanceRepository, LoanTransactionRepository loanTransactionRepository) { return new ProgressiveLoanTransactionValidatorImpl(fromApiJsonHelper, loanTransactionValidator, loanRepositoryWrapper, - loanCapitalizedIncomeBalanceRepository, loanTransactionRepository); + loanCapitalizedIncomeBalanceRepository, loanBuydownFeesBalanceRepository, loanTransactionRepository); } @Bean diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java index 5397617ab..9d5fbef25 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java @@ -149,6 +149,10 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess if (transactionType.isBuyDownFee()) { createJournalEntriesForBuyDownFee(loanDTO, loanTransactionDTO, office); } + // Handle Buy Down Fee Adjustment + if (transactionType.isBuyDownFeeAdjustment()) { + createJournalEntriesForBuyDownFeeAdjustment(loanDTO, loanTransactionDTO, office); + } } } @@ -436,6 +440,28 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } } + private void createJournalEntriesForBuyDownFeeAdjustment(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO, + final Office office) { + // loan properties + final Long loanProductId = loanDTO.getLoanProductId(); + final Long loanId = loanDTO.getLoanId(); + final String currencyCode = loanDTO.getCurrencyCode(); + + // transaction properties + final String transactionId = loanTransactionDTO.getTransactionId(); + final LocalDate transactionDate = loanTransactionDTO.getTransactionDate(); + final BigDecimal amount = loanTransactionDTO.getAmount(); + final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId(); + + if (MathUtil.isGreaterThanZero(amount)) { + // Mirror of Buy Down Fee entries (as per PS-2574 requirements) + // Debit: Deferred Income Liability, Credit: Buy Down Expense + this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), + AccrualAccountsForLoan.BUY_DOWN_EXPENSE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, + transactionDate, amount); + } + } + private void createJournalEntriesForInterestPaymentWaiverOrInterestRefund(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) { final Long loanProductId = loanDTO.getLoanProductId(); 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 60441e2a7..e5f503de9 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 @@ -710,6 +710,8 @@ public class LoanTransactionsApiResource { commandRequest = builder.chargebackTransaction(resolvedLoanId, resolvedTransactionId).build(); } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.CAPITALIZED_INCOME_ADJUSTMENT_TRANSACTION_COMMAND)) { commandRequest = builder.capitalizedIncomeAdjustment(resolvedLoanId, resolvedTransactionId).build(); + } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) { + commandRequest = builder.buyDownFeeAdjustment(resolvedLoanId, resolvedTransactionId).build(); } else { // Default to adjust the Loan Transaction commandRequest = builder.adjustTransaction(resolvedLoanId, resolvedTransactionId).build(); } 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 7e1a619d5..1f94fa781 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 @@ -383,10 +383,13 @@ public class LoanAccountConfiguration { LoanAssembler loanAssembler, LoanTransactionRepository loanTransactionRepository, PaymentDetailWritePlatformService paymentDetailWritePlatformService, LoanJournalEntryPoster loanJournalEntryPoster, NoteWritePlatformService noteWritePlatformService, ExternalIdFactory externalIdFactory, - LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository) { + LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository, + ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanBalanceService loanBalanceService, + LoanLifecycleStateMachine loanLifecycleStateMachine, BusinessEventNotifierService businessEventNotifierService) { return new BuyDownFeeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository, paymentDetailWritePlatformService, loanJournalEntryPoster, noteWritePlatformService, externalIdFactory, - loanBuyDownFeesBalanceRepository); + loanBuyDownFeesBalanceRepository, reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine, + businessEventNotifierService); } @Bean 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 28ddd5e82..4307ea39e 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 @@ -207,4 +207,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0189_add_loan_buydown_fee_event.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0189_add_loan_buydown_fee_event.xml new file mode 100644 index 000000000..b21f78690 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0189_add_loan_buydown_fee_event.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java index 99f0284a6..9ac262fca 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java @@ -109,7 +109,8 @@ public class ExternalEventConfigurationValidationServiceTest { "LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent", "LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "LoanTransactionContractTerminationPostBusinessEvent", "LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent", - "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent"); + "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent", + "LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent"); List tenants = Arrays .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); @@ -200,7 +201,8 @@ public class ExternalEventConfigurationValidationServiceTest { "LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent", "LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "LoanTransactionContractTerminationPostBusinessEvent", "LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent", - "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent"); + "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent", + "LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent"); List tenants = Arrays .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanBuyDownFeeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanBuyDownFeeTest.java index 4ce6ed308..786e20783 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanBuyDownFeeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanBuyDownFeeTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import java.math.BigDecimal; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.UUID; import lombok.extern.slf4j.Slf4j; @@ -32,6 +33,7 @@ import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest; import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.Utils; @@ -243,6 +245,168 @@ public class LoanBuyDownFeeTest extends BaseLoanIntegrationTest { .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE).locale("en").dateFormat("dd MMMM yyyy"); } + @Test + public void testBuyDownFeeAdjustment() { + runAt("06 September 2024", () -> { + // Add initial buy down fee + Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 500.0, "06 September 2024"); + assertNotNull(buyDownFeeTransactionId); + + // Create buy down fee adjustment (use same date as business date) + PostLoansLoanIdTransactionsResponse adjustmentResponse = loanTransactionHelper.buyDownFeeAdjustment(loanId, + buyDownFeeTransactionId, "06 September 2024", 100.0); + + assertNotNull(adjustmentResponse); + assertNotNull(adjustmentResponse.getLoanId()); + assertNotNull(adjustmentResponse.getClientId()); + assertNotNull(adjustmentResponse.getOfficeId()); + assertEquals(loanId, adjustmentResponse.getLoanId()); + + // Verify loan details show both transactions + final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + assertNotNull(loanDetails); + + List transactions = loanDetails.getTransactions(); + assertTrue(transactions.size() >= 3); // Disbursement, Buy Down Fee, Buy Down Fee Adjustment + + // Find the buy down fee adjustment transaction + GetLoansLoanIdTransactions adjustmentTransaction = transactions.stream() + .filter(txn -> "Buy Down Fee Adjustment".equals(txn.getType().getValue())).findFirst().orElse(null); + + assertNotNull(adjustmentTransaction); + assertEquals(0, BigDecimal.valueOf(100.0).compareTo(adjustmentTransaction.getAmount())); + assertEquals("06 September 2024", adjustmentTransaction.getDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy"))); + }); + } + + @Test + public void testBuyDownFeeAdjustmentValidations() { + runAt("08 September 2024", () -> { + // Add initial buy down fee + Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 300.0, "08 September 2024"); + assertNotNull(buyDownFeeTransactionId); + + // Test 1: Adjustment amount more than original amount (should fail) + try { + loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, "08 September 2024", 400.0); + assertTrue(false, "Expected validation error for adjustment amount exceeding original amount"); + } catch (Exception e) { + log.info("Expected validation error for excessive adjustment amount: {}", e.getMessage()); + assertTrue(e.getMessage().contains("amount") || e.getMessage().contains("exceed")); + } + + // Test 2: Adjustment date before original transaction date (should fail) + try { + loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, "07 September 2024", 100.0); + assertTrue(false, "Expected validation error for adjustment date before original transaction date"); + } catch (Exception e) { + log.info("Expected validation error for early adjustment date: {}", e.getMessage()); + assertTrue(e.getMessage().contains("date") || e.getMessage().contains("before")); + } + + // Test 3: Valid adjustment should succeed + PostLoansLoanIdTransactionsResponse validAdjustment = loanTransactionHelper.buyDownFeeAdjustment(loanId, + buyDownFeeTransactionId, "08 September 2024", 150.0); + assertNotNull(validAdjustment); + + // Test 4: Second adjustment that would exceed total should fail + try { + loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, "08 September 2024", 200.0); + assertTrue(false, "Expected validation error for total adjustments exceeding original amount"); + } catch (Exception e) { + log.info("Expected validation error for cumulative adjustment excess: {}", e.getMessage()); + assertTrue(e.getMessage().contains("amount") || e.getMessage().contains("exceed")); + } + }); + } + + @Test + public void testBuyDownFeeAdjustmentAccountingEntries() { + runAt("10 September 2024", () -> { + // Add initial buy down fee + Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 400.0, "10 September 2024"); + assertNotNull(buyDownFeeTransactionId); + + // Verify initial buy down fee accounting entries + verifyTRJournalEntries(buyDownFeeTransactionId, debit(buyDownExpenseAccount, 400.0), + credit(deferredIncomeLiabilityAccount, 400.0)); + + // Create buy down fee adjustment + PostLoansLoanIdTransactionsResponse adjustmentResponse = loanTransactionHelper.buyDownFeeAdjustment(loanId, + buyDownFeeTransactionId, "10 September 2024", 120.0); + assertNotNull(adjustmentResponse); + }); + } + + @Test + public void testMultipleBuyDownFeeAdjustments() { + runAt("12 September 2024", () -> { + // Add initial buy down fee + Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 600.0, "12 September 2024"); + assertNotNull(buyDownFeeTransactionId); + + // First adjustment + PostLoansLoanIdTransactionsResponse adjustment1 = loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, + "12 September 2024", 100.0); + assertNotNull(adjustment1); + + // Second adjustment + PostLoansLoanIdTransactionsResponse adjustment2 = loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, + "12 September 2024", 150.0); + assertNotNull(adjustment2); + + // Verify both adjustments are recorded + final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + assertNotNull(loanDetails); + + List adjustmentTransactions = loanDetails.getTransactions().stream() + .filter(txn -> "Buy Down Fee Adjustment".equals(txn.getType().getValue())).toList(); + + assertEquals(2, adjustmentTransactions.size()); + + // Verify total adjustment amounts + BigDecimal totalAdjustments = adjustmentTransactions.stream().map(GetLoansLoanIdTransactions::getAmount).reduce(BigDecimal.ZERO, + BigDecimal::add); + assertEquals(0, BigDecimal.valueOf(250.0).compareTo(totalAdjustments)); + + // Third adjustment that would exceed limit should fail + try { + loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, "12 September 2024", 400.0); + assertTrue(false, "Expected validation error for total adjustments exceeding original amount"); + } catch (Exception e) { + log.info("Expected validation error for cumulative adjustments exceeding limit: {}", e.getMessage()); + } + }); + } + + @Test + public void testBuyDownFeeAdjustmentWithExternalId() { + runAt("16 September 2024", () -> { + // Add initial buy down fee + Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 350.0, "16 September 2024"); + assertNotNull(buyDownFeeTransactionId); + + // Create adjustment with external ID + String adjustmentExternalId = UUID.randomUUID().toString(); + PostLoansLoanIdTransactionsResponse adjustmentResponse = loanTransactionHelper.buyDownFeeAdjustment(loanId, + buyDownFeeTransactionId, + new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate("16 September 2024").transactionAmount(80.0) + .externalId(adjustmentExternalId).note("Buy Down Fee Adjustment with external ID").dateFormat("dd MMMM yyyy") + .locale("en")); + + assertNotNull(adjustmentResponse); + assertEquals(adjustmentExternalId, adjustmentResponse.getResourceExternalId()); + + // Verify adjustment transaction details + final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId); + GetLoansLoanIdTransactions adjustmentTransaction = loanDetails.getTransactions().stream() + .filter(txn -> adjustmentExternalId.equals(txn.getExternalId())).findFirst().orElse(null); + + assertNotNull(adjustmentTransaction); + assertEquals(0, BigDecimal.valueOf(80.0).compareTo(adjustmentTransaction.getAmount())); + }); + } + /** * Helper method to add buy down fee for a loan * 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 4e3819e47..a8c4300cb 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,6 +636,16 @@ public class ExternalEventConfigurationHelper { loanTransactionUndoContractTerminationBusinessEvent.put("enabled", false); defaults.add(loanTransactionUndoContractTerminationBusinessEvent); + Map loanBuyDownFeeTransactionCreatedBusinessEvent = new HashMap<>(); + loanBuyDownFeeTransactionCreatedBusinessEvent.put("type", "LoanBuyDownFeeTransactionCreatedBusinessEvent"); + loanBuyDownFeeTransactionCreatedBusinessEvent.put("enabled", false); + defaults.add(loanBuyDownFeeTransactionCreatedBusinessEvent); + + Map loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent = new HashMap<>(); + loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.put("type", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent"); + loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.put("enabled", false); + defaults.add(loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent); + return defaults; } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 9b060bdd6..bec059a9f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -963,6 +963,36 @@ public class LoanTransactionHelper { .transactionAmount(amount).transactionDate(transactionDate).dateFormat("dd MMMM yyyy").locale("en")); } + public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanId, final Long buyDownFeeTransactionId, + final PostLoansLoanIdTransactionsTransactionIdRequest request) { + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction(loanId, buyDownFeeTransactionId, + request, "buyDownFeeAdjustment")); + } + + public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final String loanExternalId, final Long transactionId, + final PostLoansLoanIdTransactionsTransactionIdRequest request) { + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, + request, "buyDownFeeAdjustment")); + } + + public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final String loanExternalId, final String transactionExternalId, + final PostLoansLoanIdTransactionsTransactionIdRequest request) { + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction3(loanExternalId, + transactionExternalId, request, "buyDownFeeAdjustment")); + } + + public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanId, final String transactionExternalId, + final PostLoansLoanIdTransactionsTransactionIdRequest request) { + return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, + request, "buyDownFeeAdjustment")); + } + + public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanId, final Long buyDownFeeTransactionId, + final String transactionDate, final double amount) { + return buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, new PostLoansLoanIdTransactionsTransactionIdRequest() + .transactionAmount(amount).transactionDate(transactionDate).dateFormat("dd MMMM yyyy").locale("en")); + } + // TODO: Rewrite to use fineract-client instead! // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, // org.apache.fineract.client.models.PostLoansLoanIdRequest)