FINERACT-1981: Fix Disbursement on overpaid loan (Cumulative)

This commit is contained in:
Adam Saghy
2024-03-04 11:13:11 +01:00
parent ce255916df
commit 8803c554f5
7 changed files with 425 additions and 237 deletions
@@ -2967,8 +2967,24 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
Money downPaymentMoney = Money.of(getCurrency(),
MathUtil.percentageOf(disbursementTransaction.getAmount(), disbursedAmountPercentageForDownPayment, 19));
Money adjustedDownPaymentMoney = MathUtil
.negativeToZero(downPaymentMoney.minus(disbursementTransaction.getOverPaymentPortion(getCurrency())));
Money adjustedDownPaymentMoney = switch (getLoanProductRelatedDetail().getLoanScheduleType()) {
// For Cumulative loan: To check whether the loan was overpaid when the disbursement happened and to get the
// proper amount after the disbursement we are using two balances:
// 1. Whether the loan is still overpaid after the disbursement,
// 2. if the loan is not overpaid anymore after the disbursement, but was it more overpaid than the
// calculated down-payment amount?
case CUMULATIVE -> {
if (getTotalOverpaidAsMoney().isGreaterThanZero()) {
yield Money.zero(getCurrency());
}
yield MathUtil.negativeToZero(downPaymentMoney.minus(MathUtil.negativeToZero(disbursementTransaction
.getAmount(getCurrency()).minus(disbursementTransaction.getOutstandingLoanBalanceMoney(getCurrency())))));
}
// For Progressive loan: Disbursement transaction portion balances are enough to see whether the overpayment
// amount was more than the calculated down-payment amount
case PROGRESSIVE ->
MathUtil.negativeToZero(downPaymentMoney.minus(disbursementTransaction.getOverPaymentPortion(getCurrency())));
};
if (adjustedDownPaymentMoney.isGreaterThanZero()) {
LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), adjustedDownPaymentMoney, null, disbursedOn,
externalId);
@@ -3602,7 +3618,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
for (final LoanTransaction previousTransaction : loanTransactions) {
if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed()
&& !DateUtils.isAfter(currentTransactionDate, previousTransaction.getTransactionDate())) {
&& (DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())
|| (DateUtils.isEqual(currentTransactionDate, previousTransaction.getTransactionDate())
&& ((loanTransaction.getId() == null && previousTransaction.getId() == null)
|| (loanTransaction.getId() != null && (previousTransaction.getId() == null
|| loanTransaction.getId().compareTo(previousTransaction.getId()) < 0)))))) {
isChronologicallyLatestRepaymentOrWaiver = false;
break;
}
@@ -5958,7 +5978,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) {
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()))
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
} else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
Money transactionOutstanding = loanTransaction.getPrincipalPortion(getCurrency());
if (!loanTransaction.getOverPaymentPortion(getCurrency()).isZero()) {
@@ -5977,8 +5997,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
}
outstanding = outstanding.plus(transactionOutstanding);
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
} else {
if (this.loanInterestRecalculationDetails != null
&& this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
@@ -5987,7 +6006,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} else {
outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency()));
}
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
}
}
}
@@ -46,6 +46,7 @@ import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.portfolio.account.data.AccountTransferData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
@@ -147,14 +148,16 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
}
public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail,
public static LoanTransaction disbursement(final Loan loan, final Money amount, final PaymentDetail paymentDetail,
final LocalDate disbursementDate, final ExternalId externalId, final Money loanTotalOverpaid) {
// We need to set the overpayment amount because it could happen the transaction got saved before the proper
// portion calculation and side effect would be reverse-replay
Money overPaymentPortion = amount.isGreaterThan(loanTotalOverpaid) ? loanTotalOverpaid : amount;
LoanTransaction disbursement = new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail,
LoanTransaction disbursement = new LoanTransaction(null, loan.getOffice(), LoanTransactionType.DISBURSEMENT, paymentDetail,
amount.getAmount(), disbursementDate, externalId);
disbursement.setOverPayments(overPaymentPortion);
if (LoanScheduleType.PROGRESSIVE.equals(loan.getLoanProductRelatedDetail().getLoanScheduleType())) {
Money overPaymentPortion = amount.isGreaterThan(loanTotalOverpaid) ? loanTotalOverpaid : amount;
disbursement.setOverPayments(overPaymentPortion);
}
return disbursement;
}
@@ -911,6 +914,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
return outstandingLoanBalance;
}
public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency currency) {
return Money.of(currency, this.outstandingLoanBalance);
}
public PaymentDetail getPaymentDetail() {
return this.paymentDetail;
}