This is an automated email from the ASF dual-hosted git repository.
adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git
The following commit(s) were added to refs/heads/develop by this push:
new d56cf85dc FINERACT-1968: Fix overpayment calculation
d56cf85dc is described below
commit d56cf85dcdfc32c0568d325577344b71aaf7b354
Author: Adam Saghy <[email protected]>
AuthorDate: Fri Jan 19 10:54:08 2024 +0100
FINERACT-1968: Fix overpayment calculation
---
.../portfolio/loanaccount/domain/Loan.java | 13 ++--
.../loanaccount/domain/LoanTransaction.java | 5 --
...tLoanRepaymentScheduleTransactionProcessor.java | 82 +++++++---------------
.../LoanRepaymentScheduleTransactionProcessor.java | 2 +-
.../domain/transactionprocessor/MoneyHolder.java | 32 +++++++++
...dvancedPaymentScheduleTransactionProcessor.java | 42 ++++++-----
.../LoanChargeWritePlatformServiceImpl.java | 3 +-
...cedPaymentScheduleTransactionProcessorTest.java | 10 ++-
...backOnPaymentTypeRepaymentTransactionsTest.java | 4 --
9 files changed, 100 insertions(+), 93 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index dbd14e80a..0235c8b6b 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -111,6 +111,7 @@ import
org.apache.fineract.portfolio.loanaccount.data.LoanCollateralManagementDa
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import
org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import
org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
import
org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
@@ -797,7 +798,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
final Set<LoanCharge> loanCharges = new HashSet<>(1);
loanCharges.add(charge);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargesPayment,
getCurrency(), chargePaymentInstallments,
- loanCharges, getTotalOverpaidAsMoney());
+ loanCharges, new MoneyHolder(getTotalOverpaidAsMoney()));
updateLoanSummaryDerivedFields();
doPostLoanTransactionChecks(chargesPayment.getTransactionDate(),
loanLifecycleStateMachine);
@@ -3321,7 +3322,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
if (isTransactionChronologicallyLatest && adjustedTransaction == null
&& (!reprocess ||
!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) &&
!isForeclosure()) {
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
getCurrency(),
- getRepaymentScheduleInstallments(), getActiveCharges(),
getTotalOverpaidAsMoney());
+ getRepaymentScheduleInstallments(), getActiveCharges(),
new MoneyHolder(getTotalOverpaidAsMoney()));
reprocess = false;
if
(this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (currentInstallment == null ||
currentInstallment.isNotFullyPaidOff()) {
@@ -3914,7 +3915,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
addLoanTransaction(loanTransaction);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
loanCurrency(),
- getRepaymentScheduleInstallments(), getActiveCharges(),
getTotalOverpaidAsMoney());
+ getRepaymentScheduleInstallments(), getActiveCharges(),
new MoneyHolder(getTotalOverpaidAsMoney()));
updateLoanSummaryDerivedFields();
}
@@ -4019,7 +4020,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
addLoanTransaction(loanTransaction);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
loanCurrency(),
- getRepaymentScheduleInstallments(),
getActiveCharges(), getTotalOverpaidAsMoney());
+ getRepaymentScheduleInstallments(),
getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()));
updateLoanSummaryDerivedFields();
} else if (totalOutstanding.isGreaterThanZero()) {
@@ -6373,7 +6374,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
// If is a refund
if (adjustedTransaction == null) {
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction,
getCurrency(),
- getRepaymentScheduleInstallments(), getActiveCharges(),
getTotalOverpaidAsMoney());
+ getRepaymentScheduleInstallments(), getActiveCharges(),
new MoneyHolder(getTotalOverpaidAsMoney()));
} else {
final List<LoanTransaction>
allNonContraTransactionsPostDisbursement =
retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail =
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
@@ -6404,7 +6405,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom {
addLoanTransaction(chargebackTransaction);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction,
getCurrency(),
- getRepaymentScheduleInstallments(), getActiveCharges(),
getTotalOverpaidAsMoney());
+ getRepaymentScheduleInstallments(), getActiveCharges(), new
MoneyHolder(getTotalOverpaidAsMoney()));
updateLoanSummaryDerivedFields();
if
(!doPostLoanTransactionChecks(chargebackTransaction.getTransactionDate(),
loanLifecycleStateMachine)) {
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 bca005632..7226e6f23 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
@@ -483,11 +483,6 @@ public class LoanTransaction extends
AbstractAuditableWithUTCDateTimeCustom {
.plus(getPenaltyChargesPortion(currency)).getAmount();
}
- public void updateOverPayments(final Money overPayment) {
- final MonetaryCurrency currency = overPayment.getCurrency();
- this.overPaymentPortion =
defaultToNullIfZero(getOverPaymentPortion(currency).plus(overPayment).getAmount());
- }
-
public void setOverPayments(final Money overPayment) {
if (overPayment != null) {
this.overPaymentPortion =
defaultToNullIfZero(overPayment.getAmount());
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index 8ca79d243..89a2ec7b4 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -144,7 +144,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (unprocessed.isGreaterThanZero()) {
onLoanOverpayment(loanTransaction, unprocessed);
- loanTransaction.updateOverPayments(unprocessed);
+ loanTransaction.setOverPayments(unprocessed);
}
} else {
@@ -152,6 +152,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
}
+ MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
for (final LoanTransaction loanTransaction :
transactionsToBeProcessed) {
// TODO: analyze and remove this
if
(!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN))
{
@@ -163,7 +164,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
if (loanTransaction.isRepaymentLikeType() ||
loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
// pass through for new transactions
if (loanTransaction.getId() == null) {
- processLatestTransaction(loanTransaction, currency,
installments, charges, null);
+ processLatestTransaction(loanTransaction, currency,
installments, charges, overpaymentHolder);
loanTransaction.adjustInterestComponent(currency);
} else {
/**
@@ -174,7 +175,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
// Reset derived component of new loan transaction and
// re-process transaction
- processLatestTransaction(newLoanTransaction, currency,
installments, charges, null);
+ processLatestTransaction(newLoanTransaction, currency,
installments, charges, overpaymentHolder);
newLoanTransaction.adjustInterestComponent(currency);
/**
* Check if the transaction amounts have changed. If so,
reverse the original transaction and update
@@ -195,9 +196,11 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
loanTransaction.resetDerivedComponents();
handleRefund(loanTransaction, currency, installments, charges);
} else if (loanTransaction.isCreditBalanceRefund()) {
- recalculateCreditTransaction(changedTransactionDetail,
loanTransaction, currency, installments, transactionsToBeProcessed);
+ recalculateCreditTransaction(changedTransactionDetail,
loanTransaction, currency, installments, transactionsToBeProcessed,
+ overpaymentHolder);
} else if (loanTransaction.isChargeback()) {
- recalculateCreditTransaction(changedTransactionDetail,
loanTransaction, currency, installments, transactionsToBeProcessed);
+ recalculateCreditTransaction(changedTransactionDetail,
loanTransaction, currency, installments, transactionsToBeProcessed,
+ overpaymentHolder);
reprocessChargebackTransactionRelation(changedTransactionDetail,
transactionsToBeProcessed);
} else if (loanTransaction.isChargeOff()) {
recalculateChargeOffTransaction(changedTransactionDetail,
loanTransaction, currency, installments);
@@ -209,11 +212,11 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
@Override
public void processLatestTransaction(final LoanTransaction
loanTransaction, final MonetaryCurrency currency,
- final List<LoanRepaymentScheduleInstallment> installments, final
Set<LoanCharge> charges, Money overpaidAmount) {
+ final List<LoanRepaymentScheduleInstallment> installments, final
Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
switch (loanTransaction.getTypeOf()) {
case WRITEOFF -> handleWriteOff(loanTransaction, currency,
installments);
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction,
currency, installments, charges);
- case CHARGEBACK -> handleChargeback(loanTransaction, currency,
overpaidAmount, installments);
+ case CHARGEBACK -> handleChargeback(loanTransaction, currency,
installments, overpaymentHolder);
default -> {
Money transactionAmountUnprocessed =
handleTransactionAndCharges(loanTransaction, currency, installments, charges,
null,
false);
@@ -223,8 +226,11 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
transactionAmountUnprocessed.zero(),
transactionAmountUnprocessed.zero());
} else {
onLoanOverpayment(loanTransaction,
transactionAmountUnprocessed);
-
loanTransaction.updateOverPayments(transactionAmountUnprocessed);
+
loanTransaction.setOverPayments(transactionAmountUnprocessed);
}
+
overpaymentHolder.setMoneyObject(transactionAmountUnprocessed);
+ } else {
+ overpaymentHolder.setMoneyObject(Money.zero(currency));
}
}
}
@@ -435,17 +441,15 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
private void recalculateCreditTransaction(ChangedTransactionDetail
changedTransactionDetail, LoanTransaction loanTransaction,
- MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment>
installments,
- List<LoanTransaction> transactionsToBeProcessed) {
+ MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment>
installments, List<LoanTransaction> transactionsToBeProcessed,
+ MoneyHolder overpaymentHolder) {
// pass through for new transactions
if (loanTransaction.getId() == null) {
return;
}
final LoanTransaction newLoanTransaction =
LoanTransaction.copyTransactionProperties(loanTransaction);
- List<LoanTransaction> mergedList =
getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
- Money overpaidAmount = calculateOverpaidAmount(loanTransaction,
mergedList, installments, currency);
- processCreditTransaction(newLoanTransaction, overpaidAmount, currency,
installments);
+ processCreditTransaction(newLoanTransaction, overpaymentHolder,
currency, installments);
if (!LoanTransaction.transactionAmountsMatch(currency,
loanTransaction, newLoanTransaction)) {
createNewTransaction(loanTransaction, newLoanTransaction,
changedTransactionDetail);
}
@@ -470,40 +474,7 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
}
- private Money calculateOverpaidAmount(LoanTransaction loanTransaction,
List<LoanTransaction> transactions,
- List<LoanRepaymentScheduleInstallment> installments,
MonetaryCurrency currency) {
- Money totalPaidInRepayments = Money.zero(currency);
-
- Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
- for (final LoanRepaymentScheduleInstallment scheduledRepayment :
installments) {
- cumulativeTotalPaidOnInstallments =
cumulativeTotalPaidOnInstallments
-
.plus(scheduledRepayment.getPrincipalCompleted(currency).plus(scheduledRepayment.getInterestPaid(currency)))
-
.plus(scheduledRepayment.getFeeChargesPaid(currency)).plus(scheduledRepayment.getPenaltyChargesPaid(currency));
- }
-
- for (final LoanTransaction transaction : transactions) {
- if (transaction.isReversed()) {
- continue;
- }
- if (transaction.equals(loanTransaction)) {
- // We want to process only the transactions prior to the
actual one
- break;
- }
- if (transaction.isRefund() || transaction.isRefundForActiveLoan())
{
- totalPaidInRepayments =
totalPaidInRepayments.minus(transaction.getAmount(currency));
- } else if (transaction.isCreditBalanceRefund() ||
transaction.isChargeback()) {
- totalPaidInRepayments =
totalPaidInRepayments.minus(transaction.getOverPaymentPortion(currency));
- } else if (transaction.isRepaymentLikeType()) {
- totalPaidInRepayments =
totalPaidInRepayments.plus(transaction.getAmount(currency));
- }
- }
-
- // if total paid in transactions higher than repayment schedule then
- // theres an overpayment.
- return
MathUtil.negativeToZero(totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments));
- }
-
- private void processCreditTransaction(LoanTransaction loanTransaction,
Money overpaidAmount, MonetaryCurrency currency,
+ private void processCreditTransaction(LoanTransaction loanTransaction,
MoneyHolder overpaymentHolder, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments) {
loanTransaction.resetDerivedComponents();
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings =
new ArrayList<>();
@@ -511,9 +482,10 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
installments.sort(byDate);
final Money zeroMoney = Money.zero(currency);
Money transactionAmount = loanTransaction.getAmount(currency);
- Money principalPortion =
MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaidAmount));
+ Money principalPortion =
MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaymentHolder.getMoneyObject()));
Money repaidAmount =
MathUtil.negativeToZero(transactionAmount.minus(principalPortion));
- loanTransaction.updateOverPayments(repaidAmount);
+ loanTransaction.setOverPayments(repaidAmount);
+
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(repaidAmount));
loanTransaction.updateComponents(principalPortion, zeroMoney,
zeroMoney, zeroMoney);
if (principalPortion.isGreaterThanZero()) {
@@ -770,14 +742,14 @@ public abstract class
AbstractLoanRepaymentScheduleTransactionProcessor implemen
loanTransaction.updateComponentsAndTotal(principalPortion,
interestPortion, feeChargesPortion, penaltychargesPortion);
}
- protected void handleChargeback(LoanTransaction loanTransaction,
MonetaryCurrency currency, Money overpaidAmount,
- List<LoanRepaymentScheduleInstallment> installments) {
- processCreditTransaction(loanTransaction, overpaidAmount, currency,
installments);
+ protected void handleChargeback(LoanTransaction loanTransaction,
MonetaryCurrency currency,
+ List<LoanRepaymentScheduleInstallment> installments, MoneyHolder
overpaidAmountHolder) {
+ processCreditTransaction(loanTransaction, overpaidAmountHolder,
currency, installments);
}
- protected void handleCreditBalanceRefund(LoanTransaction loanTransaction,
MonetaryCurrency currency, Money overpaidAmount,
- List<LoanRepaymentScheduleInstallment> installments) {
- processCreditTransaction(loanTransaction, overpaidAmount, currency,
installments);
+ protected void handleCreditBalanceRefund(LoanTransaction loanTransaction,
MonetaryCurrency currency,
+ List<LoanRepaymentScheduleInstallment> installments, MoneyHolder
overpaidAmountHolder) {
+ processCreditTransaction(loanTransaction, overpaidAmountHolder,
currency, installments);
}
protected void handleRefund(LoanTransaction loanTransaction,
MonetaryCurrency currency,
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
index efcfba19b..ebf16d6a7 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java
@@ -41,7 +41,7 @@ public interface LoanRepaymentScheduleTransactionProcessor {
* schedule.
*/
void processLatestTransaction(LoanTransaction loanTransaction,
MonetaryCurrency currency,
- List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, Money overpaidAmount);
+ List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, MoneyHolder overpaymentHolder);
/**
* Provides support for passing all {@link LoanTransaction}'s so it will
completely re-process the entire loan
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/MoneyHolder.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/MoneyHolder.java
new file mode 100644
index 000000000..f967e8ffb
--- /dev/null
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/MoneyHolder.java
@@ -0,0 +1,32 @@
+/**
+ * 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.portfolio.loanaccount.domain.transactionprocessor;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.fineract.organisation.monetary.domain.Money;
+
+@Getter
+@Setter
+@AllArgsConstructor
+public class MoneyHolder {
+
+ private Money moneyObject;
+}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index 295b354d6..c63291b5a 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -55,6 +55,7 @@ import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import
org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanproduct.domain.DueType;
import
org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
@@ -136,9 +137,10 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
List<ChargeOrTransaction> chargeOrTransactions =
createSortedChargesAndTransactionsList(loanTransactions, charges);
final ChangedTransactionDetail changedTransactionDetail = new
ChangedTransactionDetail();
+ MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(currency));
for (final ChargeOrTransaction chargeOrTransaction :
chargeOrTransactions) {
chargeOrTransaction.getLoanTransaction().ifPresent(loanTransaction
-> processSingleTransaction(loanTransaction, currency,
- installments, charges, changedTransactionDetail));
+ installments, charges, changedTransactionDetail,
overpaymentHolder));
chargeOrTransaction.getLoanCharge()
.ifPresent(loanCharge -> processSingleCharge(loanCharge,
currency, installments, disbursementDate));
}
@@ -150,18 +152,18 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
@Override
public void processLatestTransaction(LoanTransaction loanTransaction,
MonetaryCurrency currency,
- List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, Money overpaidAmount) {
+ List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
switch (loanTransaction.getTypeOf()) {
case DISBURSEMENT -> handleDisbursement(loanTransaction, currency,
installments);
case WRITEOFF -> handleWriteOff(loanTransaction, currency,
installments);
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction,
currency, installments, charges);
- case CHARGEBACK -> handleChargeback(loanTransaction, currency,
overpaidAmount, installments);
- case CREDIT_BALANCE_REFUND ->
handleCreditBalanceRefund(loanTransaction, currency, overpaidAmount,
installments);
+ case CHARGEBACK -> handleChargeback(loanTransaction, currency,
installments, overpaymentHolder);
+ case CREDIT_BALANCE_REFUND ->
handleCreditBalanceRefund(loanTransaction, currency, installments,
overpaymentHolder);
case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND,
GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
WAIVE_INTEREST, RECOVERY_REPAYMENT ->
- handleRepayment(loanTransaction, currency, installments,
charges);
+ handleRepayment(loanTransaction, currency, installments,
charges, overpaymentHolder);
case CHARGE_OFF -> handleChargeOff(loanTransaction, currency,
installments);
- case CHARGE_PAYMENT -> handleChargePayment(loanTransaction,
currency, installments, charges);
+ case CHARGE_PAYMENT -> handleChargePayment(loanTransaction,
currency, installments, charges, overpaymentHolder);
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will
not be processed.");
// TODO: Cover rest of the transaction types
default -> {
@@ -226,10 +228,10 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
private void processSingleTransaction(LoanTransaction loanTransaction,
MonetaryCurrency currency,
- List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges,
- ChangedTransactionDetail changedTransactionDetail) {
+ List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, ChangedTransactionDetail changedTransactionDetail,
+ MoneyHolder overpaymentHolder) {
if (loanTransaction.getId() == null) {
- processLatestTransaction(loanTransaction, currency, installments,
charges, Money.zero(currency));
+ processLatestTransaction(loanTransaction, currency, installments,
charges, overpaymentHolder);
if (loanTransaction.isInterestWaiver()) {
loanTransaction.adjustInterestComponent(currency);
}
@@ -242,7 +244,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
// Reset derived component of new loan transaction and
// re-process transaction
- processLatestTransaction(newLoanTransaction, currency,
installments, charges, Money.zero(currency));
+ processLatestTransaction(newLoanTransaction, currency,
installments, charges, overpaymentHolder);
if (loanTransaction.isInterestWaiver()) {
newLoanTransaction.adjustInterestComponent(currency);
}
@@ -322,12 +324,12 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
private void handleRepayment(LoanTransaction loanTransaction,
MonetaryCurrency currency,
- List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges) {
+ List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
if (loanTransaction.isRepaymentLikeType() ||
loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents();
}
Money transactionAmountUnprocessed =
loanTransaction.getAmount(currency);
- processTransaction(loanTransaction, currency, installments,
transactionAmountUnprocessed, charges);
+ processTransaction(loanTransaction, currency, installments,
transactionAmountUnprocessed, charges, overpaymentHolder);
}
private LoanTransactionToRepaymentScheduleMapping getTransactionMapping(
@@ -399,10 +401,13 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal,
aggregatedInterest, aggregatedFee, aggregatedPenalty);
}
- private void handleOverpayment(Money overpaymentPortion, LoanTransaction
loanTransaction) {
+ private void handleOverpayment(Money overpaymentPortion, LoanTransaction
loanTransaction, MoneyHolder overpaymentHolder) {
if (overpaymentPortion.isGreaterThanZero()) {
onLoanOverpayment(loanTransaction, overpaymentPortion);
- loanTransaction.updateOverPayments(overpaymentPortion);
+ overpaymentHolder.setMoneyObject(overpaymentPortion);
+ loanTransaction.setOverPayments(overpaymentPortion);
+ } else {
+ overpaymentHolder.setMoneyObject(overpaymentPortion.zero());
}
}
@@ -427,7 +432,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
private void handleChargePayment(LoanTransaction loanTransaction,
MonetaryCurrency currency,
- List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges) {
+ List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
Money zero = Money.zero(currency);
Money feeChargesPortion = zero;
Money penaltyChargesPortion = zero;
@@ -473,7 +478,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
if (unprocessed.isGreaterThanZero()) {
- processTransaction(loanTransaction, currency, installments,
unprocessed, charges);
+ processTransaction(loanTransaction, currency, installments,
unprocessed, charges, overpaymentHolder);
}
}
@@ -658,7 +663,8 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
}
private void processTransaction(LoanTransaction loanTransaction,
MonetaryCurrency currency,
- List<LoanRepaymentScheduleInstallment> installments, Money
transactionAmountUnprocessed, Set<LoanCharge> charges) {
+ List<LoanRepaymentScheduleInstallment> installments, Money
transactionAmountUnprocessed, Set<LoanCharge> charges,
+ MoneyHolder overpaymentHolder) {
Money zero = Money.zero(currency);
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings =
new ArrayList<>();
@@ -683,7 +689,7 @@ public class AdvancedPaymentScheduleTransactionProcessor
extends AbstractLoanRep
balances.getAggregatedFeeChargesPortion(),
balances.getAggregatedPenaltyChargesPortion());
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
- handleOverpayment(transactionAmountUnprocessed, loanTransaction);
+ handleOverpayment(transactionAmountUnprocessed, loanTransaction,
overpaymentHolder);
}
private Money processPeriodsHorizontally(LoanTransaction loanTransaction,
MonetaryCurrency currency,
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index 5e96c43d9..d6391836f 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -114,6 +114,7 @@ import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationT
import
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
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.MoneyHolder;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import
org.apache.fineract.portfolio.loanaccount.exception.InstallmentNotFoundException;
import
org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
@@ -832,7 +833,7 @@ public class LoanChargeWritePlatformServiceImpl implements
LoanChargeWritePlatfo
final LoanRepaymentScheduleTransactionProcessor
loanRepaymentScheduleTransactionProcessor =
loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy());
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction,
loan.getCurrency(),
- loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges(), loan.getTotalOverpaidAsMoney());
+ loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()));
loan.addLoanTransaction(loanChargeAdjustmentTransaction);
loan.updateLoanSummaryAndStatus();
diff --git
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
index be3f43f5b..e2ae5fa40 100644
---
a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
+++
b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
@@ -43,6 +43,7 @@ import
org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRul
import
org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
+import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import
org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import
org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
@@ -121,7 +122,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
Mockito.when(charge.updatePaidAmountBy(refEq(chargeAmountMoney),
eq(1), refEq(zero))).thenReturn(chargeAmountMoney);
Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
- underTest.processLatestTransaction(loanTransaction, currency,
List.of(installment), Set.of(charge), overpaidAmount);
+ underTest.processLatestTransaction(loanTransaction, currency,
List.of(installment), Set.of(charge),
+ new MoneyHolder(overpaidAmount));
Mockito.verify(installment,
Mockito.times(1)).payFeeChargesComponent(eq(transactionDate),
eq(chargeAmountMoney));
Mockito.verify(loanTransaction,
Mockito.times(1)).updateComponents(refEq(zero), refEq(zero),
refEq(chargeAmountMoney), refEq(zero));
@@ -165,7 +167,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
Mockito.when(charge.updatePaidAmountBy(refEq(transactionAmountMoney),
eq(1), refEq(zero))).thenReturn(transactionAmountMoney);
Mockito.when(loanTransaction.isPenaltyPayment()).thenReturn(false);
- underTest.processLatestTransaction(loanTransaction, currency,
List.of(installment), Set.of(charge), overpaidAmount);
+ underTest.processLatestTransaction(loanTransaction, currency,
List.of(installment), Set.of(charge),
+ new MoneyHolder(overpaidAmount));
Mockito.verify(installment,
Mockito.times(1)).payFeeChargesComponent(eq(transactionDate),
eq(transactionAmountMoney));
Mockito.verify(loanTransaction,
Mockito.times(1)).updateComponents(refEq(zero), refEq(zero),
refEq(transactionAmountMoney),
@@ -218,7 +221,8 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
Mockito.when(loanPaymentAllocationRule.getAllocationTypes()).thenReturn(List.of(PaymentAllocationType.DUE_PRINCIPAL));
Mockito.when(loanTransaction.isOn(eq(transactionDate))).thenReturn(true);
- underTest.processLatestTransaction(loanTransaction, currency,
List.of(installment), Set.of(charge), overpaidAmount);
+ underTest.processLatestTransaction(loanTransaction, currency,
List.of(installment), Set.of(charge),
+ new MoneyHolder(overpaidAmount));
Mockito.verify(installment,
Mockito.times(1)).payFeeChargesComponent(eq(transactionDate),
eq(chargeAmountMoney));
Mockito.verify(loanTransaction,
Mockito.times(1)).updateComponents(refEq(zero), refEq(zero),
refEq(chargeAmountMoney), refEq(zero));
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackOnPaymentTypeRepaymentTransactionsTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackOnPaymentTypeRepaymentTransactionsTest.java
index b92c9893a..2f6e81210 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackOnPaymentTypeRepaymentTransactionsTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargebackOnPaymentTypeRepaymentTransactionsTest.java
@@ -54,7 +54,6 @@ import
org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHe
import
org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
@@ -65,7 +64,6 @@ import org.junit.jupiter.params.provider.MethodSource;
public class LoanChargebackOnPaymentTypeRepaymentTransactionsTest {
private ResponseSpecification responseSpec;
- private ResponseSpecification responseSpecErr400;
private ResponseSpecification responseSpecErr503;
private RequestSpecification requestSpec;
private ClientHelper clientHelper;
@@ -77,13 +75,11 @@ public class
LoanChargebackOnPaymentTypeRepaymentTransactionsTest {
this.requestSpec = new
RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization", "Basic " +
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new
ResponseSpecBuilder().expectStatusCode(200).build();
- this.responseSpecErr400 = new
ResponseSpecBuilder().expectStatusCode(400).build();
this.responseSpecErr503 = new
ResponseSpecBuilder().expectStatusCode(503).build();
this.loanTransactionHelper = new
LoanTransactionHelper(this.requestSpec, this.responseSpec);
this.clientHelper = new ClientHelper(this.requestSpec,
this.responseSpec);
}
- @Disabled("Issue with Chargeback and Adv Pment Alloc")
@ParameterizedTest
@MethodSource("loanProductFactory")
public void
loanTransactionChargebackForPaymentTypeRepaymentTransactionTest(LoanProductTestBuilder
loanProductTestBuilder) {