FINERACT-2421: Adjust interest with MIR and CBR results in improper allocations
This commit is contained in:
committed by
Adam Saghy
parent
cc372286fa
commit
7b322481b8
@@ -1636,6 +1636,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.loanTermVariations.stream().filter(LoanTermVariations::isActive).collect(Collectors.toList());
|
return this.loanTermVariations.stream().filter(LoanTermVariations::isActive).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setLoanTermVariations(List<LoanTermVariations> loanTermVariations) {
|
||||||
|
this.loanTermVariations = loanTermVariations;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isTopup() {
|
public boolean isTopup() {
|
||||||
return this.isTopup;
|
return this.isTopup;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -179,9 +179,11 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
|
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
|
||||||
|
Loan loan = loanTransaction.getLoan();
|
||||||
// pass through for new transactions
|
// pass through for new transactions
|
||||||
if (loanTransaction.getId() == null) {
|
if (loanTransaction.getId() == null) {
|
||||||
processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null,
|
||||||
|
loan.getActiveLoanTermVariations()));
|
||||||
loanTransaction.adjustInterestComponent();
|
loanTransaction.adjustInterestComponent();
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
@@ -192,8 +194,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
|
|
||||||
// Reset derived component of new loan transaction and
|
// Reset derived component of new loan transaction and
|
||||||
// re-process transaction
|
// re-process transaction
|
||||||
processLatestTransaction(newLoanTransaction,
|
processLatestTransaction(newLoanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder,
|
||||||
new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
null, loan.getActiveLoanTermVariations()));
|
||||||
newLoanTransaction.adjustInterestComponent();
|
newLoanTransaction.adjustInterestComponent();
|
||||||
/**
|
/**
|
||||||
* Check if the transaction amounts have changed. If so, reverse the original transaction and update
|
* Check if the transaction amounts have changed. If so, reverse the original transaction and update
|
||||||
|
|||||||
+2
@@ -26,6 +26,7 @@ import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@@ -36,4 +37,5 @@ public class TransactionCtx {
|
|||||||
private final Set<LoanCharge> charges;
|
private final Set<LoanCharge> charges;
|
||||||
private final MoneyHolder overpaymentHolder;
|
private final MoneyHolder overpaymentHolder;
|
||||||
private final ChangedTransactionDetail changedTransactionDetail;
|
private final ChangedTransactionDetail changedTransactionDetail;
|
||||||
|
private final List<LoanTermVariations> activeLoanTermVariations;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -18,26 +18,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler;
|
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
import org.apache.fineract.commands.annotation.CommandType;
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
|
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
@CommandType(entity = "RESCHEDULELOAN", action = "APPROVE")
|
@CommandType(entity = "RESCHEDULELOAN", action = "APPROVE")
|
||||||
public class ApproveLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
|
public class ApproveLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
|
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public ApproveLoanRescheduleRequestCommandHandler(LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
|
|
||||||
this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
|
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
|
||||||
|
|||||||
+2
-6
@@ -18,26 +18,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler;
|
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
import org.apache.fineract.commands.annotation.CommandType;
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
|
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
@CommandType(entity = "RESCHEDULELOAN", action = "CREATE")
|
@CommandType(entity = "RESCHEDULELOAN", action = "CREATE")
|
||||||
public class CreateLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
|
public class CreateLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
|
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public CreateLoanRescheduleRequestCommandHandler(LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
|
|
||||||
this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
|
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
|
||||||
|
|||||||
+2
-6
@@ -18,26 +18,22 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler;
|
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.handler;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.commands.annotation.CommandType;
|
import org.apache.fineract.commands.annotation.CommandType;
|
||||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
|
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestWritePlatformService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
@CommandType(entity = "RESCHEDULELOAN", action = "REJECT")
|
@CommandType(entity = "RESCHEDULELOAN", action = "REJECT")
|
||||||
public class RejectLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
|
public class RejectLoanRescheduleRequestCommandHandler implements NewCommandSourceHandler {
|
||||||
|
|
||||||
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
|
private final LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public RejectLoanRescheduleRequestCommandHandler(LoanRescheduleRequestWritePlatformService loanRescheduleRequestWritePlatformService) {
|
|
||||||
this.loanRescheduleRequestWritePlatformService = loanRescheduleRequestWritePlatformService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
|
public CommandProcessingResult processCommand(JsonCommand jsonCommand) {
|
||||||
|
|||||||
+3
-1
@@ -22,6 +22,7 @@ import java.time.LocalDate;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
@@ -33,5 +34,6 @@ public interface InterestRefundService {
|
|||||||
|
|
||||||
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
|
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
|
||||||
Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor processor, Long loanId,
|
Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor processor, Long loanId,
|
||||||
LocalDate relatedRefundTransactionDate, List<LoanTransaction> newTransactions, List<Long> oldTransactionIds);
|
LocalDate relatedRefundTransactionDate, List<LoanTransaction> newTransactions, List<Long> oldTransactionIds,
|
||||||
|
List<LoanTermVariations> activeLoanTermVariations);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -597,7 +597,7 @@ public class LoanChargeService {
|
|||||||
loanCharges.add(charge);
|
loanCharges.add(charge);
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargesPayment,
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargesPayment,
|
||||||
new TransactionCtx(loan.getCurrency(), chargePaymentInstallments, loanCharges,
|
new TransactionCtx(loan.getCurrency(), chargePaymentInstallments, loanCharges,
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
|
|
||||||
loanLifecycleStateMachine.determineAndTransition(loan, chargesPayment.getTransactionDate());
|
loanLifecycleStateMachine.determineAndTransition(loan, chargesPayment.getTransactionDate());
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -140,7 +140,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
|
|||||||
if (processLatest) {
|
if (processLatest) {
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
if (!loan.isProgressiveSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
if (!loan.isProgressiveSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||||
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
|
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
|
||||||
reprocessOnPostConditions = true;
|
reprocessOnPostConditions = true;
|
||||||
|
|||||||
+1
-1
@@ -83,7 +83,7 @@ public class LoanRefundService {
|
|||||||
|
|
||||||
loadTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
loadTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
|
|
||||||
loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate());
|
loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate());
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -57,7 +57,8 @@ public class ProgressivePossibleNextRepaymentCalculationServiceImpl extends Abst
|
|||||||
ProgressiveLoanInterestScheduleModel scheduleModel = optionalScheduleModel.get();
|
ProgressiveLoanInterestScheduleModel scheduleModel = optionalScheduleModel.get();
|
||||||
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
|
||||||
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), repaymentScheduleInstallments, Set.of(),
|
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), repaymentScheduleInstallments, Set.of(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel);
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), scheduleModel,
|
||||||
|
loan.getActiveLoanTermVariations());
|
||||||
ctx.setChargedOff(loan.isChargedOff());
|
ctx.setChargedOff(loan.isChargedOff());
|
||||||
ctx.setWrittenOff(loan.isClosedWrittenOff());
|
ctx.setWrittenOff(loan.isClosedWrittenOff());
|
||||||
ctx.setContractTerminated(loan.isContractTermination());
|
ctx.setContractTerminated(loan.isContractTermination());
|
||||||
|
|||||||
+2
-2
@@ -229,7 +229,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments,
|
ProgressiveLoanInterestScheduleModel scheduleModel = emiCalculator.generateInstallmentInterestScheduleModel(installments,
|
||||||
LoanConfigurationDetailsMapper.map(loan), installmentAmountInMultiplesOf, overpaymentHolder.getMoneyObject().getMc());
|
LoanConfigurationDetailsMapper.map(loan), installmentAmountInMultiplesOf, overpaymentHolder.getMoneyObject().getMc());
|
||||||
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
|
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, charges, overpaymentHolder,
|
||||||
changedTransactionDetail, scheduleModel);
|
changedTransactionDetail, scheduleModel, loan.getActiveLoanTermVariations());
|
||||||
|
|
||||||
List<ChangeOperation> changeOperations = createSortedChangeList(loanTermVariations, loanTransactions, charges);
|
List<ChangeOperation> changeOperations = createSortedChangeList(loanTermVariations, loanTransactions, charges);
|
||||||
|
|
||||||
@@ -571,7 +571,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
.filter(LoanTransaction::isNotReversed).filter(tr -> tr.getId() == null).toList());
|
.filter(LoanTransaction::isNotReversed).filter(tr -> tr.getId() == null).toList());
|
||||||
if (validateInterestRefundTransactionRelation(loanTransaction)) {
|
if (validateInterestRefundTransactionRelation(loanTransaction)) {
|
||||||
final Money interestAfterRefund = interestRefundService.totalInterestByTransactions(this, loan.getId(), targetDate,
|
final Money interestAfterRefund = interestRefundService.totalInterestByTransactions(this, loan.getId(), targetDate,
|
||||||
modifiedTransactions, unmodifiedTransactionIds);
|
modifiedTransactions, unmodifiedTransactionIds, ctx.getActiveLoanTermVariations());
|
||||||
final Money newAmount = interestBeforeRefund.minus(progCtx.getSumOfInterestRefundAmount()).minus(interestAfterRefund);
|
final Money newAmount = interestBeforeRefund.minus(progCtx.getSumOfInterestRefundAmount()).minus(interestAfterRefund);
|
||||||
loanTransaction.updateAmount(newAmount.getAmount());
|
loanTransaction.updateAmount(newAmount.getAmount());
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-4
@@ -28,6 +28,7 @@ import org.apache.fineract.organisation.monetary.domain.Money;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||||
@@ -52,14 +53,16 @@ public class ProgressiveTransactionCtx extends TransactionCtx {
|
|||||||
|
|
||||||
public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
||||||
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
|
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
|
||||||
ProgressiveLoanInterestScheduleModel model) {
|
ProgressiveLoanInterestScheduleModel model, List<LoanTermVariations> activeLoanTermVariations) {
|
||||||
this(currency, installments, charges, overpaymentHolder, changedTransactionDetail, model, Money.zero(currency));
|
this(currency, installments, charges, overpaymentHolder, changedTransactionDetail, model, Money.zero(currency),
|
||||||
|
activeLoanTermVariations);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
public ProgressiveTransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
|
||||||
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
|
Set<LoanCharge> charges, MoneyHolder overpaymentHolder, ChangedTransactionDetail changedTransactionDetail,
|
||||||
ProgressiveLoanInterestScheduleModel model, Money sumOfInterestRefundAmount) {
|
ProgressiveLoanInterestScheduleModel model, Money sumOfInterestRefundAmount,
|
||||||
super(currency, installments, charges, overpaymentHolder, changedTransactionDetail);
|
List<LoanTermVariations> activeLoanTermVariations) {
|
||||||
|
super(currency, installments, charges, overpaymentHolder, changedTransactionDetail, activeLoanTermVariations);
|
||||||
this.sumOfInterestRefundAmount = sumOfInterestRefundAmount;
|
this.sumOfInterestRefundAmount = sumOfInterestRefundAmount;
|
||||||
this.model = model;
|
this.model = model;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -112,7 +112,8 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
|
|||||||
savedModel = extractModel(progressiveLoanModel);
|
savedModel = extractModel(progressiveLoanModel);
|
||||||
if (savedModel.isPresent() && progressiveLoanModel.get().getBusinessDate().isBefore(businessDate)) {
|
if (savedModel.isPresent() && progressiveLoanModel.get().getBusinessDate().isBefore(businessDate)) {
|
||||||
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
||||||
Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), savedModel.get());
|
Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), savedModel.get(),
|
||||||
|
loan.getActiveLoanTermVariations());
|
||||||
ctx.setChargedOff(loan.isChargedOff());
|
ctx.setChargedOff(loan.isChargedOff());
|
||||||
ctx.setWrittenOff(loan.isClosedWrittenOff());
|
ctx.setWrittenOff(loan.isClosedWrittenOff());
|
||||||
ctx.setContractTerminated(loan.isContractTermination());
|
ctx.setContractTerminated(loan.isContractTermination());
|
||||||
|
|||||||
+14
-11
@@ -164,8 +164,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
|
when(charge.updatePaidAmountBy(refEq(chargeAmountMoney), eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
|
||||||
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||||
|
|
||||||
underTest.processLatestTransaction(loanTransaction,
|
underTest.processLatestTransaction(loanTransaction, new TransactionCtx(currency, List.of(installment), Set.of(charge),
|
||||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null));
|
new MoneyHolder(overpaidAmount), null, loan.getActiveLoanTermVariations()));
|
||||||
|
|
||||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
|
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(chargeAmountMoney));
|
||||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
||||||
@@ -208,8 +208,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
|
when(charge.updatePaidAmountBy(refEq(transactionAmountMoney), eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
|
||||||
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
when(loanTransaction.isPenaltyPayment()).thenReturn(false);
|
||||||
|
|
||||||
underTest.processLatestTransaction(loanTransaction,
|
underTest.processLatestTransaction(loanTransaction, new TransactionCtx(currency, List.of(installment), Set.of(charge),
|
||||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null));
|
new MoneyHolder(overpaidAmount), null, loan.getActiveLoanTermVariations()));
|
||||||
|
|
||||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney));
|
Mockito.verify(installment, times(1)).payFeeChargesComponent(eq(transactionDate), eq(transactionAmountMoney));
|
||||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney), refEq(zero));
|
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(transactionAmountMoney), refEq(zero));
|
||||||
@@ -260,8 +260,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
|
when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
|
||||||
when(loanTransaction.isOn(transactionDate)).thenReturn(true);
|
when(loanTransaction.isOn(transactionDate)).thenReturn(true);
|
||||||
|
|
||||||
underTest.processLatestTransaction(loanTransaction,
|
underTest.processLatestTransaction(loanTransaction, new TransactionCtx(currency, List.of(installment), Set.of(charge),
|
||||||
new TransactionCtx(currency, List.of(installment), Set.of(charge), new MoneyHolder(overpaidAmount), null));
|
new MoneyHolder(overpaidAmount), null, loan.getActiveLoanTermVariations()));
|
||||||
|
|
||||||
Mockito.verify(installment, times(1)).payFeeChargesComponent(transactionDate, chargeAmountMoney);
|
Mockito.verify(installment, times(1)).payFeeChargesComponent(transactionDate, chargeAmountMoney);
|
||||||
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
Mockito.verify(loanTransaction, times(1)).updateComponents(refEq(zero), refEq(zero), refEq(chargeAmountMoney), refEq(zero));
|
||||||
@@ -289,7 +289,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
installments.add(installment);
|
installments.add(installment);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null);
|
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null,
|
||||||
|
loan.getActiveLoanTermVariations());
|
||||||
underTest.processCreditTransaction(chargebackTransaction, ctx);
|
underTest.processCreditTransaction(chargebackTransaction, ctx);
|
||||||
|
|
||||||
// verify principal
|
// verify principal
|
||||||
@@ -346,7 +347,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
installments.add(installment);
|
installments.add(installment);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null);
|
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null,
|
||||||
|
loan.getActiveLoanTermVariations());
|
||||||
underTest.processCreditTransaction(chargebackTransaction, ctx);
|
underTest.processCreditTransaction(chargebackTransaction, ctx);
|
||||||
|
|
||||||
// verify charges on installment
|
// verify charges on installment
|
||||||
@@ -398,7 +400,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
installments.add(installment2);
|
installments.add(installment2);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null);
|
TransactionCtx ctx = new TransactionCtx(MONETARY_CURRENCY, installments, null, overpaymentHolder, null,
|
||||||
|
loan.getActiveLoanTermVariations());
|
||||||
underTest.processCreditTransaction(chargebackTransaction, ctx);
|
underTest.processCreditTransaction(chargebackTransaction, ctx);
|
||||||
|
|
||||||
// verify principal
|
// verify principal
|
||||||
@@ -491,7 +494,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
|
|
||||||
// Set up TransactionCtx with installments and charges
|
// Set up TransactionCtx with installments and charges
|
||||||
TransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, Set.of(), overpaymentHolder, changedTransactionDetail,
|
TransactionCtx ctx = new ProgressiveTransactionCtx(currency, installments, Set.of(), overpaymentHolder, changedTransactionDetail,
|
||||||
model, null);
|
model, loan.getActiveLoanTermVariations());
|
||||||
|
|
||||||
// Mock additional necessary methods
|
// Mock additional necessary methods
|
||||||
LoanCharge loanCharge = mock(LoanCharge.class);
|
LoanCharge loanCharge = mock(LoanCharge.class);
|
||||||
@@ -570,7 +573,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
when(model.getMaturityDate()).thenReturn(LocalDate.of(2023, 12, 31));
|
when(model.getMaturityDate()).thenReturn(LocalDate.of(2023, 12, 31));
|
||||||
|
|
||||||
TransactionCtx ctx = new ProgressiveTransactionCtx(currency, spyInstallments, Set.of(), new MoneyHolder(Money.zero(currency)),
|
TransactionCtx ctx = new ProgressiveTransactionCtx(currency, spyInstallments, Set.of(), new MoneyHolder(Money.zero(currency)),
|
||||||
mock(ChangedTransactionDetail.class), model, Money.zero(currency));
|
mock(ChangedTransactionDetail.class), model, Money.zero(currency), loan.getActiveLoanTermVariations());
|
||||||
|
|
||||||
underTest.processLatestTransaction(disbursementTransaction, ctx);
|
underTest.processLatestTransaction(disbursementTransaction, ctx);
|
||||||
|
|
||||||
|
|||||||
+7
-5
@@ -189,10 +189,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), refundTransaction.getTransactionDate(),
|
Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), refundTransaction.getTransactionDate(),
|
||||||
List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
|
List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList(),
|
||||||
|
loan.getActiveLoanTermVariations());
|
||||||
Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(),
|
Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(),
|
||||||
refundTransaction.getTransactionDate(), List.of(refundTransaction),
|
refundTransaction.getTransactionDate(), List.of(refundTransaction),
|
||||||
loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
|
loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList(), loan.getActiveLoanTermVariations());
|
||||||
BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount();
|
BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount();
|
||||||
|
|
||||||
if (MathUtil.isZero(interestRefundAmount)) {
|
if (MathUtil.isZero(interestRefundAmount)) {
|
||||||
@@ -846,12 +847,13 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
if (processLatest) {
|
if (processLatest) {
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), refundTransaction,
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), refundTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
loan.getLoanTransactions().add(refundTransaction);
|
loan.getLoanTransactions().add(refundTransaction);
|
||||||
if (interestRefundTransaction != null) {
|
if (interestRefundTransaction != null) {
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(),
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(),
|
||||||
interestRefundTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
interestRefundTransaction,
|
||||||
loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
loan.addLoanTransaction(interestRefundTransaction);
|
loan.addLoanTransaction(interestRefundTransaction);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+6
-5
@@ -444,11 +444,6 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
} else {
|
} else {
|
||||||
loanSchedule.updateLoanSchedule(loan, loanScheduleDTO.getLoanScheduleModel());
|
loanSchedule.updateLoanSchedule(loan, loanScheduleDTO.getLoanScheduleModel());
|
||||||
}
|
}
|
||||||
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
|
|
||||||
loanChargeService.recalculateAllCharges(loan);
|
|
||||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
|
||||||
|
|
||||||
this.loanRepaymentScheduleHistoryRepository.saveAll(loanRepaymentScheduleHistoryList);
|
|
||||||
|
|
||||||
loan.updateRescheduledByUser(appUser);
|
loan.updateRescheduledByUser(appUser);
|
||||||
loan.updateRescheduledOnDate(DateUtils.getBusinessLocalDate());
|
loan.updateRescheduledOnDate(DateUtils.getBusinessLocalDate());
|
||||||
@@ -456,6 +451,12 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
// update the status of the request
|
// update the status of the request
|
||||||
loanRescheduleRequest.approve(appUser, approvedOnDate);
|
loanRescheduleRequest.approve(appUser, approvedOnDate);
|
||||||
|
|
||||||
|
loanAccrualsProcessingService.reprocessExistingAccruals(loan, true);
|
||||||
|
loanChargeService.recalculateAllCharges(loan);
|
||||||
|
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||||
|
|
||||||
|
this.loanRepaymentScheduleHistoryRepository.saveAll(loanRepaymentScheduleHistoryList);
|
||||||
|
|
||||||
Optional<LocalDate> lastTransactionDateForReprocessing = loanTransactionRepository.findLastTransactionDateForReprocessing(loan);
|
Optional<LocalDate> lastTransactionDateForReprocessing = loanTransactionRepository.findLastTransactionDateForReprocessing(loan);
|
||||||
if (lastTransactionDateForReprocessing.isPresent()) {
|
if (lastTransactionDateForReprocessing.isPresent()) {
|
||||||
loanLifecycleStateMachine.determineAndTransition(loan, lastTransactionDateForReprocessing.get());
|
loanLifecycleStateMachine.determineAndTransition(loan, lastTransactionDateForReprocessing.get());
|
||||||
|
|||||||
+4
-2
@@ -2244,9 +2244,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
|
|
||||||
final InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan);
|
final InterestRefundService interestRefundService = interestRefundServiceDelegate.lookupInterestRefundService(loan);
|
||||||
final Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(),
|
final Money totalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(),
|
||||||
List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
|
List.of(), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList(),
|
||||||
|
loan.getActiveLoanTermVariations());
|
||||||
final Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(),
|
final Money newTotalInterest = interestRefundService.totalInterestByTransactions(null, loan.getId(), targetTxn.getTransactionDate(),
|
||||||
List.of(targetTxn), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList());
|
List.of(targetTxn), loan.getLoanTransactions().stream().map(AbstractPersistableCustom::getId).toList(),
|
||||||
|
loan.getActiveLoanTermVariations());
|
||||||
final BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount();
|
final BigDecimal interestRefundAmount = totalInterest.minus(newTotalInterest).getAmount();
|
||||||
final Collection<PaymentTypeData> paymentTypeOptions = paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
final Collection<PaymentTypeData> paymentTypeOptions = paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.INTEREST_REFUND);
|
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.INTEREST_REFUND);
|
||||||
|
|||||||
+1
-1
@@ -211,7 +211,7 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
|
|||||||
ProgressiveLoanInterestScheduleModel model = savedModel.get();
|
ProgressiveLoanInterestScheduleModel model = savedModel.get();
|
||||||
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
|
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
|
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
|
||||||
new ChangedTransactionDetail(), model, getTotalRefundInterestAmount(loan));
|
new ChangedTransactionDetail(), model, getTotalRefundInterestAmount(loan), loan.getActiveLoanTermVariations());
|
||||||
progressiveContext.getAlreadyProcessedTransactions().addAll(loanTransactionService.retrieveListOfTransactionsForReprocessing(loan));
|
progressiveContext.getAlreadyProcessedTransactions().addAll(loanTransactionService.retrieveListOfTransactionsForReprocessing(loan));
|
||||||
progressiveContext.setChargedOff(loan.isChargedOff());
|
progressiveContext.setChargedOff(loan.isChargedOff());
|
||||||
progressiveContext.setWrittenOff(loan.isClosedWrittenOff());
|
progressiveContext.setWrittenOff(loan.isClosedWrittenOff());
|
||||||
|
|||||||
+7
-6
@@ -964,8 +964,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
if (isTransactionChronologicallyLatest && (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled())
|
if (isTransactionChronologicallyLatest && (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled())
|
||||||
&& !loan.isForeclosure()) {
|
&& !loan.isForeclosure()) {
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(),
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(),
|
||||||
newInterestPaymentWaiverTransaction, new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
newInterestPaymentWaiverTransaction,
|
||||||
loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
|
|
||||||
reprocess = false;
|
reprocess = false;
|
||||||
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
if (loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||||
@@ -2993,7 +2994,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
if (processLatest) {
|
if (processLatest) {
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), interestRefundTxn,
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), interestRefundTxn,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
loan.addLoanTransaction(interestRefundTxn);
|
loan.addLoanTransaction(interestRefundTxn);
|
||||||
} else {
|
} else {
|
||||||
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
if (loan.isCumulativeSchedule() && loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||||
@@ -3053,7 +3054,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loan.addLoanTransaction(chargebackTransaction);
|
loan.addLoanTransaction(chargebackTransaction);
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction,
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
}
|
}
|
||||||
loanLifecycleStateMachine.determineAndTransition(loan, chargebackTransaction.getTransactionDate());
|
loanLifecycleStateMachine.determineAndTransition(loan, chargebackTransaction.getTransactionDate());
|
||||||
}
|
}
|
||||||
@@ -3253,7 +3254,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loan.addLoanTransaction(loanTransaction);
|
loan.addLoanTransaction(loanTransaction);
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
}
|
}
|
||||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan);
|
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING, loan);
|
||||||
@@ -3325,7 +3326,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loan.addLoanTransaction(loanTransaction);
|
loan.addLoanTransaction(loanTransaction);
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null, loan.getActiveLoanTermVariations()));
|
||||||
|
|
||||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
} else if (totalOutstanding.isGreaterThanZero()) {
|
} else if (totalOutstanding.isGreaterThanZero()) {
|
||||||
|
|||||||
+4
-1
@@ -35,6 +35,7 @@ import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
@@ -119,8 +120,10 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
|
@Transactional(readOnly = true, propagation = Propagation.REQUIRES_NEW)
|
||||||
public Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor processor, final Long loanId,
|
public Money totalInterestByTransactions(LoanRepaymentScheduleTransactionProcessor processor, final Long loanId,
|
||||||
LocalDate relatedRefundTransactionDate, List<LoanTransaction> newTransactions, List<Long> oldTransactionIds) {
|
LocalDate relatedRefundTransactionDate, List<LoanTransaction> newTransactions, List<Long> oldTransactionIds,
|
||||||
|
List<LoanTermVariations> activeLoanTermVariations) {
|
||||||
Loan loan = loanAssembler.assembleFrom(loanId);
|
Loan loan = loanAssembler.assembleFrom(loanId);
|
||||||
|
loan.setLoanTermVariations(activeLoanTermVariations);
|
||||||
if (processor == null) {
|
if (processor == null) {
|
||||||
processor = loanTransactionProcessingService.getTransactionProcessor(loan.getTransactionProcessingStrategyCode());
|
processor = loanTransactionProcessingService.getTransactionProcessor(loan.getTransactionProcessingStrategyCode());
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -100,7 +100,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
|
|
||||||
final ProgressiveTransactionCtx progressiveTransactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(),
|
final ProgressiveTransactionCtx progressiveTransactionCtx = new ProgressiveTransactionCtx(loan.getCurrency(),
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
|
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
|
||||||
new ChangedTransactionDetail(), savedModel.get());
|
new ChangedTransactionDetail(), savedModel.get(), loan.getActiveLoanTermVariations());
|
||||||
progressiveTransactionCtx.setChargedOff(loan.isChargedOff());
|
progressiveTransactionCtx.setChargedOff(loan.isChargedOff());
|
||||||
progressiveTransactionCtx.setWrittenOff(loan.isClosedWrittenOff());
|
progressiveTransactionCtx.setWrittenOff(loan.isClosedWrittenOff());
|
||||||
progressiveTransactionCtx.setContractTerminated(loan.isContractTermination());
|
progressiveTransactionCtx.setContractTerminated(loan.isContractTermination());
|
||||||
@@ -108,7 +108,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
transactionCtx = new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
transactionCtx = new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail());
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), loan.getActiveLoanTermVariations());
|
||||||
}
|
}
|
||||||
|
|
||||||
final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService
|
final ChangedTransactionDetail changedTransactionDetail = loanTransactionProcessingService
|
||||||
|
|||||||
+65
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.integrationtests;
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
import io.restassured.builder.RequestSpecBuilder;
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
import io.restassured.builder.ResponseSpecBuilder;
|
import io.restassured.builder.ResponseSpecBuilder;
|
||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
@@ -25,11 +27,15 @@ import io.restassured.specification.RequestSpecification;
|
|||||||
import io.restassured.specification.ResponseSpecification;
|
import io.restassured.specification.ResponseSpecification;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
|
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
|
||||||
|
import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansResponse;
|
import org.apache.fineract.client.models.PostLoansResponse;
|
||||||
|
import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
|
||||||
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
import org.apache.fineract.integrationtests.common.BusinessDateHelper;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
|
import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper;
|
||||||
@@ -153,6 +159,65 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMerchantIssuedRefundAndCreditBalanceRefundWithAdjustSchedule() {
|
||||||
|
final AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
|
||||||
|
runAt("24 September 2025", () -> {
|
||||||
|
final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
final Long loanId = createLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND", "24 September 2025", 116.89,
|
||||||
|
35.99, 3);
|
||||||
|
loanIdRef.set(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(116.89), "24 September 2025");
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("26 September 2025", () -> {
|
||||||
|
executeInlineCOB(loanIdRef.get());
|
||||||
|
addRepaymentForLoan(loanIdRef.get(), 117.12, "26 September 2025");
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("06 October 2025", () -> {
|
||||||
|
executeInlineCOB(loanIdRef.get());
|
||||||
|
loanTransactionHelper.makeMerchantIssuedRefund(loanIdRef.get(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("06 October 2025").locale(LOCALE).transactionAmount(8.13));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanIdRef.get());
|
||||||
|
// Validate Loan is Overpaid
|
||||||
|
assertTrue(loanDetails.getStatus().getOverpaid());
|
||||||
|
|
||||||
|
validateLoanSummaryBalances(loanDetails, 0.00, 117.12, 0.00, 116.89, 8.14);
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("07 October 2025", () -> {
|
||||||
|
executeInlineCOB(loanIdRef.get());
|
||||||
|
loanTransactionHelper.makeCreditBalanceRefund(loanIdRef.get(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("07 October 2025").locale(LOCALE).transactionAmount(8.14));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanIdRef.get());
|
||||||
|
// Validate Loan is Closed
|
||||||
|
assertTrue(loanDetails.getStatus().getClosedObligationsMet());
|
||||||
|
validateLoanSummaryBalances(loanDetails, 0.00, 117.12, 0.00, 116.89, null);
|
||||||
|
|
||||||
|
PostCreateRescheduleLoansResponse rescheduleLoansResponse = loanRescheduleRequestHelper//
|
||||||
|
.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest()//
|
||||||
|
.loanId(loanIdRef.get())//
|
||||||
|
.rescheduleReasonId(1L)//
|
||||||
|
.rescheduleFromDate("25 September 2025").dateFormat(DATETIME_PATTERN).locale(LOCALE)//
|
||||||
|
.submittedOnDate("07 October 2025")//
|
||||||
|
.newInterestRate(BigDecimal.valueOf(25.99)));//
|
||||||
|
|
||||||
|
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(), //
|
||||||
|
new PostUpdateRescheduleLoansRequest()//
|
||||||
|
.approvedOnDate("07 October 2025").locale(LOCALE).dateFormat(DATETIME_PATTERN));//
|
||||||
|
|
||||||
|
loanDetails = loanTransactionHelper.getLoanDetails(loanIdRef.get());
|
||||||
|
// Validate Loan is Overpaid
|
||||||
|
assertTrue(loanDetails.getStatus().getOverpaid());
|
||||||
|
|
||||||
|
validateLoanSummaryBalances(loanDetails, 0.00, 117.06, 0.00, 116.89, 0.06);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Long createAndDisburseLoanForMerchantIssuedRefundWithInterestRefund(Long clientId) {
|
private Long createAndDisburseLoanForMerchantIssuedRefundWithInterestRefund(Long clientId) {
|
||||||
return createAndDisburseLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND");
|
return createAndDisburseLoanForRefundWithInterestRefund(clientId, "MERCHANT_ISSUED_REFUND");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user