FINERACT-2421: Disbursement charge amount incorrect calculation

This commit is contained in:
Jose Alberto Hernandez
2026-02-05 10:41:19 +01:00
committed by Adam Saghy
parent 2620c61afa
commit 1d1876edfd
22 changed files with 136 additions and 116 deletions
@@ -216,7 +216,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
// Calculate available amount: (approved + over applied) - expected tranches - disbursed - capitalized income
if (loan.isMultiDisburmentLoan() && loan.getDisbursementDetails() != null) {
final BigDecimal expectedDisbursementAmount = loan.getDisbursementDetails().stream()
.filter(detail -> detail.actualDisbursementDate() == null).map(LoanDisbursementDetails::principal)
.filter(detail -> detail.actualDisbursementDate() == null).map(LoanDisbursementDetails::getPrincipal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
approvedWithOverApplied = approvedWithOverApplied.subtract(expectedDisbursementAmount);
}
@@ -22,6 +22,8 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
@@ -30,6 +32,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
*/
@RequiredArgsConstructor
@Getter
@Accessors(chain = true)
public final class DisbursementData implements LoanPrincipalRelatedDataHolder, Comparable<DisbursementData> {
private final Long id;
@@ -41,6 +44,8 @@ public final class DisbursementData implements LoanPrincipalRelatedDataHolder, C
private final String loanChargeId;
private final BigDecimal chargeAmount;
private final BigDecimal waivedChargeAmount;
@Setter
private BigDecimal disburseChargeAmount;
// import fields
private transient Integer rowIndex;
@@ -814,7 +814,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
if (isMultiDisburmentLoan()) {
for (LoanDisbursementDetails disbursementDetail : getDisbursementDetails()) {
if (disbursementDetail.actualDisbursementDate() != null) {
principal = principal.add(disbursementDetail.principal());
principal = principal.add(disbursementDetail.getPrincipal());
}
}
return principal;
@@ -999,7 +999,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
if (!details.isEmpty()) {
principal = BigDecimal.ZERO;
for (LoanDisbursementDetails disbursementDetails : details) {
principal = principal.add(disbursementDetails.principal());
principal = principal.add(disbursementDetails.getPrincipal());
}
}
return principal;
@@ -1303,7 +1303,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
public LoanDisbursementDetails getDisbursementDetails(final LocalDate transactionDate, final BigDecimal transactionAmount) {
for (LoanDisbursementDetails disbursementDetail : this.disbursementDetails) {
if (!disbursementDetail.isReversed() && disbursementDetail.getDisbursementDate().equals(transactionDate)
&& (disbursementDetail.principal().compareTo(transactionAmount) == 0)) {
&& (disbursementDetail.getPrincipal().compareTo(transactionAmount) == 0)) {
return disbursementDetail;
}
}
@@ -1317,7 +1317,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
Collection<LoanDisbursementDetails> loanDisburseDetails = this.getDisbursementDetails();
for (LoanDisbursementDetails details : loanDisburseDetails) {
if (details.actualDisbursementDate() != null) {
principalAmount = principalAmount.add(details.principal());
principalAmount = principalAmount.add(details.getPrincipal());
}
}
} else if (isApproved()) {
@@ -26,10 +26,12 @@ import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Objects;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
@Getter
@Entity
@Table(name = "m_loan_disbursement_detail")
public class LoanDisbursementDetails extends AbstractPersistableCustom<Long> {
@@ -107,10 +109,6 @@ public class LoanDisbursementDetails extends AbstractPersistableCustom<Long> {
return this.actualDisbursementDate;
}
public BigDecimal principal() {
return this.principal;
}
public void updatePrincipal(BigDecimal principal) {
this.principal = principal;
}
@@ -108,7 +108,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
&& loanCharge.getLoan().isMultiDisburmentLoan()) {
for (final LoanDisbursementDetails loanDisbursementDetails : loanCharge.getLoan().getDisbursementDetails()) {
if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) {
amount = amount.add(loanDisbursementDetails.principal());
amount = amount.add(loanDisbursementDetails.getPrincipal());
}
}
} else {
@@ -132,7 +132,7 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
// Then we need to get as of this loan charge due date how much amount disbursed.
for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) {
baseAmount = MathUtil.add(baseAmount, loanDisbursementDetails.principal());
baseAmount = MathUtil.add(baseAmount, loanDisbursementDetails.getPrincipal());
}
}
} else {
@@ -94,7 +94,7 @@ public class LoanMapper {
BigDecimal waivedChargeAmount = null;
disbursementData.add(
new DisbursementData(loanDisbursementDetails.getId(), loan.getId(), expectedDisbursementDate, actualDisbursementDate,
loanDisbursementDetails.principal(), loan.getNetDisbursalAmount(), null, null, waivedChargeAmount));
loanDisbursementDetails.getPrincipal(), loan.getNetDisbursalAmount(), null, null, waivedChargeAmount));
}
return disbursementData;
@@ -88,7 +88,7 @@ public class LoanApprovedAmountWritePlatformServiceImpl implements LoanApprovedA
changes.put("oldApprovedAmount", loan.getApprovedPrincipal());
BigDecimal expectedDisbursementAmount = loan.getDisbursementDetails().stream().filter(t -> t.actualDisbursementDate() == null)
.map(LoanDisbursementDetails::principal).reduce(BigDecimal.ZERO, BigDecimal::add);
.map(LoanDisbursementDetails::getPrincipal).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal oldAvailableDisbursement = loan.getApprovedPrincipal().subtract(loan.getSummary().getTotalPrincipal())
.subtract(expectedDisbursementAmount);
@@ -187,7 +187,7 @@ public class LoanBalanceService {
charge.resetToOriginal(loan.getCurrency());
}
}
loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getDisbursedAmount().subtract(disbursementDetail.principal()));
loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getDisbursedAmount().subtract(disbursementDetail.getPrincipal()));
disbursementDetail.updateActualDisbursementDate(null);
disbursementDetail.reverse();
updateLoanSummaryDerivedFields(loan);
@@ -251,7 +251,9 @@ public class LoanChargeService {
case PERCENT_OF_AMOUNT_AND_INTEREST -> {
final BigDecimal totalInterestCharged = loan.getTotalInterest();
if (loan.isMultiDisburmentLoan() && loanCharge.isDisbursementCharge()) {
yield getTotalAllTrancheDisbursementAmount(loan).getAmount().add(totalInterestCharged);
yield sumMultiDisbursementAmounts(loan, false).getAmount().add(totalInterestCharged);
} else if (loan.isMultiDisburmentLoan() && loanCharge.isTrancheDisbursementCharge()) {
yield sumMultiDisbursementAmounts(loan, true).getAmount().add(totalInterestCharged);
} else {
yield loan.getPrincipal().getAmount().add(totalInterestCharged);
}
@@ -259,7 +261,7 @@ public class LoanChargeService {
case PERCENT_OF_INTEREST -> loan.getTotalInterest();
case PERCENT_OF_DISBURSEMENT_AMOUNT -> {
if (loanCharge.getTrancheDisbursementCharge() != null) {
yield loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().principal();
yield loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().getPrincipal();
} else {
yield loan.getPrincipal().getAmount();
}
@@ -449,7 +451,7 @@ public class LoanChargeService {
if (loanCharge.getLoan().isMultiDisburmentLoan() && loanCharge.isSpecifiedDueDate()) {
for (final LoanDisbursementDetails loanDisbursementDetails : loanCharge.getLoan().getDisbursementDetails()) {
if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) {
amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.principal());
amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.getPrincipal());
}
}
} else {
@@ -465,7 +467,7 @@ public class LoanChargeService {
break;
case PERCENT_OF_DISBURSEMENT_AMOUNT:
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = loanCharge.getLoanTrancheDisbursementCharge();
amountPercentageAppliedTo = loanTrancheDisbursementCharge.getloanDisbursementDetails().principal();
amountPercentageAppliedTo = loanTrancheDisbursementCharge.getloanDisbursementDetails().getPrincipal();
break;
default:
break;
@@ -850,14 +852,23 @@ public class LoanChargeService {
}
}
private Money getTotalAllTrancheDisbursementAmount(final Loan loan) {
Money amount = Money.zero(loan.getCurrency());
private Money sumMultiDisbursementAmounts(final Loan loan, final boolean isTrancheDisbursement) {
if (loan.isMultiDisburmentLoan()) {
for (final LoanDisbursementDetails loanDisbursementDetail : loan.getDisbursementDetails()) {
amount = amount.plus(loanDisbursementDetail.principal());
final List<LoanDisbursementDetails> loanDisbursementDetails = loan.getDisbursementDetails();
if (loan.isOpen() || !loanDisbursementDetails.isEmpty()) {
if (isTrancheDisbursement) {
return Money.of(loan.getCurrency(), loanDisbursementDetails.stream() //
.map(LoanDisbursementDetails::getPrincipal) //
.reduce(BigDecimal.ZERO, BigDecimal::add));
}
return Money.of(loan.getCurrency(), loanDisbursementDetails.get(0).getPrincipal());
} else if (loan.isSubmittedAndPendingApproval()) {
return Money.of(loan.getCurrency(), loan.getProposedPrincipal());
} else if (loan.isApproved()) {
return Money.of(loan.getCurrency(), loan.getApprovedPrincipal());
}
}
return amount;
return Money.zero(loan.getCurrency());
}
private List<Long> fetchAllLoanChargeIds(final Loan loan) {
@@ -884,15 +895,16 @@ public class LoanChargeService {
private BigDecimal getDerivedAmountForCharge(final Loan loan, final LoanCharge loanCharge) {
BigDecimal amount = BigDecimal.ZERO;
if (loan.isMultiDisburmentLoan() && loanCharge.getCharge().getChargeTimeType().equals(ChargeTimeType.DISBURSEMENT.getValue())) {
amount = loan.getApprovedPrincipal();
final ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(loanCharge.getCharge().getChargeTimeType());
if (loan.isMultiDisburmentLoan() && chargeTimeType.isDisbursementOrTrancheDisbursementCharge()) {
return sumMultiDisbursementAmounts(loan, chargeTimeType.isTrancheDisbursement()).getAmount();
} else {
// If charge type is specified due date and loan is multi disburment loan.
// Then we need to get as of this loan charge due date how much amount disbursed.
if (loanCharge.isSpecifiedDueDate() && loan.isMultiDisburmentLoan()) {
for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) {
amount = amount.add(loanDisbursementDetails.principal());
amount = amount.add(loanDisbursementDetails.getPrincipal());
}
}
} else {