You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2026/01/22 15:04:06 UTC
(fineract) 01/02: FINERACT-2354: [BE] pay-off to close re-aged loan with charge outcomes with Active loan status
This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 641c6da1b9b4f81b82281f4e94c8ba80d5eef784
Author: Soma Sörös <so...@dpc.hu>
AuthorDate: Wed Jan 21 10:23:25 2026 +0100
FINERACT-2354: [BE] pay-off to close re-aged loan with charge outcomes with Active loan status
---
.../domain/LoanRepaymentScheduleInstallment.java | 42 +++++++++++++++++++++
...dvancedPaymentScheduleTransactionProcessor.java | 44 +++++++++++++++++++++-
2 files changed, 84 insertions(+), 2 deletions(-)
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 482586035b..1330394cfb 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -945,6 +945,48 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
}
}
+ public void addPenaltyCharges(final Money amount) {
+ if (amount != null) {
+ addPenaltyCharges(amount.getAmount());
+ }
+ }
+
+ public void addPenaltyCharges(final BigDecimal amount) {
+ if (this.penaltyCharges == null) {
+ setPenaltyCharges(amount);
+ } else {
+ setPenaltyCharges(this.penaltyCharges.add(amount));
+ }
+ }
+
+ public void addToPenaltyPaid(final Money amount) {
+ if (amount != null) {
+ addToPenaltyPaid(amount.getAmount());
+ }
+ }
+
+ public void addToPenaltyPaid(final BigDecimal amount) {
+ if (this.penaltyChargesPaid == null) {
+ setPenaltyChargesPaid(amount);
+ } else {
+ setPenaltyChargesPaid(this.penaltyChargesPaid.add(amount));
+ }
+ }
+
+ public void addToFeeChargesPaid(final Money amount) {
+ if (amount != null) {
+ addToFeeChargesPaid(amount.getAmount());
+ }
+ }
+
+ public void addToFeeChargesPaid(final BigDecimal amount) {
+ if (this.feeChargesPaid == null) {
+ setFeeChargesPaid(amount);
+ } else {
+ setFeeChargesPaid(this.feeChargesPaid.add(amount));
+ }
+ }
+
/********** UNPAY COMPONENTS ****/
public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) {
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 4ed75b6031..018bce06a2 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -3764,10 +3764,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
balances.getPrincipal().getAmount(), balances.getInterest().getAmount(), balances.getFee().getAmount(),
balances.getPenalty().getAmount(), null, null, null);
+ if (!settings.isEqualInstallmentForFeesAndPenalties) {
+ // If charges should NOT reamortize by reage, we should manage to keep them related to the correct
+ // installment.
+ LoanRepaymentScheduleInstallment target = earlyRepaidInstallment;
+ Set<LoanCharge> charges = ctx.getCharges();
+ moveRelatedChargesToInstallment(charges, target, installments, currency);
+ }
+
earlyRepaidInstallment.setPrincipalCompleted(balances.getPrincipal().getAmount());
earlyRepaidInstallment.setInterestPaid(balances.getInterest().getAmount());
- earlyRepaidInstallment.setFeeChargesPaid(balances.getFee().getAmount());
- earlyRepaidInstallment.setPenaltyChargesPaid(balances.getPenalty().getAmount());
+ earlyRepaidInstallment.addToFeeChargesPaid(balances.getFee());
+ earlyRepaidInstallment.addToPenaltyPaid(balances.getPenalty());
earlyRepaidInstallment.setTotalPaidInAdvance(balances.getPaidInAdvance().getAmount());
@@ -3820,6 +3828,38 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
reprocessInstallments(installments);
}
+ private void moveRelatedChargesToInstallment(Set<LoanCharge> charges, LoanRepaymentScheduleInstallment target,
+ List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency) {
+ int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
+ Set<LoanCharge> chargesOfNewInstallment = getLoanChargesOfInstallment(charges, target, firstNormalInstallmentNumber);
+ Integer targetInstallmentNumber = target.getInstallmentNumber();
+ installments.stream().filter(i -> Objects.equals(i.getInstallmentNumber(), targetInstallmentNumber)).findFirst()
+ .filter(source -> source != target).ifPresent(source -> {
+ // move fees
+ chargesOfNewInstallment.stream().filter(LoanCharge::isNotFullyPaid).filter(LoanCharge::isFeeCharge)
+ .forEach(loanCharge -> moveFeeCharge(loanCharge, source, target, currency));
+ // move penalties
+ chargesOfNewInstallment.stream().filter(LoanCharge::isNotFullyPaid).filter(LoanCharge::isPenaltyCharge)
+ .forEach(loanCharge -> movePenaltyCharge(loanCharge, source, target, currency));
+ });
+ }
+
+ private void movePenaltyCharge(LoanCharge loanCharge, LoanRepaymentScheduleInstallment source, LoanRepaymentScheduleInstallment target,
+ MonetaryCurrency currency) {
+ source.setPenaltyCharges(MathUtil.subtract(source.getPenaltyCharges(), loanCharge.chargeAmount()));
+ source.setPenaltyChargesPaid(MathUtil.subtract(source.getPenaltyChargesPaid(), loanCharge.getAmountPaid()));
+ target.addPenaltyCharges(loanCharge.getAmount(currency));
+ target.addToPenaltyPaid(loanCharge.getAmountPaid());
+ }
+
+ private void moveFeeCharge(LoanCharge loanCharge, LoanRepaymentScheduleInstallment source, LoanRepaymentScheduleInstallment target,
+ MonetaryCurrency currency) {
+ source.setFeeChargesCharged(MathUtil.subtract(source.getFeeChargesCharged(), loanCharge.chargeAmount()));
+ source.setFeeChargesPaid(MathUtil.subtract(source.getFeeChargesPaid(), loanCharge.getAmountPaid()));
+ target.addToFeeCharges(loanCharge.getAmount(currency));
+ target.addToFeeChargesPaid(loanCharge.getAmountPaid());
+ }
+
private void createChargeMappingsForInstallment(final LoanRepaymentScheduleInstallment installment,
List<ReAgedChargeEqualAmortizationValues> reAgedChargeEqualAmortizationValues, Integer index) {
reAgedChargeEqualAmortizationValues.forEach(amortizationValue -> installment.getInstallmentCharges().add(new LoanInstallmentCharge(