FINERACT-1981: Order disbursements and repayment periods properly

This commit is contained in:
Adam Saghy
2023-11-30 12:42:44 +01:00
parent b08fd9b573
commit ea68edce2d
15 changed files with 532 additions and 519 deletions
@@ -47,7 +47,6 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -953,7 +952,7 @@ public class LoansApiResource {
loanBasicDetails.getFeeChargesAtDisbursementCharged());
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(resolvedLoanId, repaymentScheduleRelatedData,
disbursementData, loanBasicDetails.isInterestRecalculationEnabled(),
loanBasicDetails.getSummary() != null ? loanBasicDetails.getSummary().getFeeChargesPaid() : BigDecimal.ZERO);
LoanScheduleType.fromEnumOptionData(loanBasicDetails.getLoanScheduleType()));
if (associationParameters.contains(DataTableApiConstant.futureScheduleAssociateParamName)
&& loanBasicDetails.isInterestRecalculationEnabled()) {
@@ -965,8 +964,9 @@ public class LoansApiResource {
&& loanBasicDetails.isInterestRecalculationEnabled()
&& LoanStatus.fromInt(loanBasicDetails.getStatus().getId().intValue()).isActive()) {
mandatoryResponseParameters.add(DataTableApiConstant.originalScheduleAssociateParamName);
LoanScheduleData loanScheduleData = this.loanScheduleHistoryReadPlatformService
.retrieveRepaymentArchiveSchedule(resolvedLoanId, repaymentScheduleRelatedData, disbursementData);
LoanScheduleData loanScheduleData = this.loanScheduleHistoryReadPlatformService.retrieveRepaymentArchiveSchedule(
resolvedLoanId, repaymentScheduleRelatedData, disbursementData,
LoanScheduleType.fromEnumOptionData(loanBasicDetails.getLoanScheduleType()));
loanBasicDetails = LoanAccountData.withOriginalSchedule(loanBasicDetails, loanScheduleData);
}
}
@@ -28,7 +28,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
@@ -85,26 +84,6 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
boolean isFirstRepayment = true;
if (loanApplicationTerms.isMultiDisburseLoan()) {
/* fetches the first tranche amount and also updates other tranche details to map */
Money disburseAmt = Money.of(currency, getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(),
scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation()));
Money downPaymentAmt = Money.zero(currency);
if (loanApplicationTerms.isDownPaymentEnabled()) {
downPaymentAmt = Money.of(currency, MathUtil.percentageOf(disburseAmt.getAmount(),
loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19));
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, loanApplicationTerms.getInstallmentAmountInMultiplesOf());
}
}
Money remainingPrincipalAmt = disburseAmt.minus(downPaymentAmt);
scheduleParams.setPrincipalToBeScheduled(remainingPrincipalAmt);
loanApplicationTerms.setPrincipal(remainingPrincipalAmt);
scheduleParams.setOutstandingBalance(remainingPrincipalAmt);
scheduleParams.setOutstandingBalanceAsPerRest(remainingPrincipalAmt);
loanApplicationTerms.resetFixedEmiAmount();
}
// charges which depends on total loan interest will be added to this
// set and handled separately after all installments generated
final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
@@ -134,13 +113,8 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
}
// 5 determine principal,interest of repayment period
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
getPaymentPeriodsInOneYearCalculator(), currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(),
scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()),
scheduleParams.getTotalCumulativeInterest(), loanApplicationTerms.getTotalInterestDue(),
scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(),
loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, null, scheduleParams.getCompoundingMap(), null,
scheduledDueDate, null);
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(loanApplicationTerms,
scheduleParams, mc);
// update cumulative fields for principal
currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal());
@@ -241,12 +215,8 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator();
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator,
BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest,
Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance,
LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap<LocalDate, Money> principalVariation,
Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
Collection<LoanTermVariationsData> termVariations);
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms,
LoanScheduleParams loanScheduleParams, MathContext mc);
// Private, internal methods
private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
@@ -266,7 +236,8 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
Money principalToBeScheduled;
if (loanApplicationTerms.isMultiDisburseLoan()) {
if (loanApplicationTerms.getTotalDisbursedAmount().isGreaterThanZero()) {
principalToBeScheduled = loanApplicationTerms.getTotalMultiDisbursedAmount();
principalToBeScheduled = Money.of(loanApplicationTerms.getCurrency(),
loanApplicationTerms.getDisbursementDatas().get(0).getPrincipal());
} else if (loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) {
principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal();
} else {
@@ -300,6 +271,9 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
loanScheduleParams, loanApplicationTerms.getExpectedDisbursementDate(), disbursementData.getPrincipal());
periods.add(downPaymentPeriod);
}
} else {
loanScheduleParams.getDisburseDetailMap().put(disbursementData.disbursementDate(),
Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal()));
}
}
@@ -313,14 +287,19 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, loanApplicationTerms.getInstallmentAmountInMultiplesOf());
}
LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod
.downPayment(scheduleParams.getInstalmentNumber(), date, downPaymentAmount, scheduleParams.getOutstandingBalance());
LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod.downPayment(
scheduleParams.getInstalmentNumber(), date, downPaymentAmount,
scheduleParams.getOutstandingBalance().minus(downPaymentAmount));
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
scheduleParams.incrementInstalmentNumber();
scheduleParams.addTotalRepaymentExpected(downPaymentAmount);
scheduleParams.reduceOutstandingBalance(downPaymentAmount);
scheduleParams.reduceOutstandingBalanceAsPerRest(downPaymentAmount);
scheduleParams.setPrincipalToBeScheduled(scheduleParams.getPrincipalToBeScheduled().minus(downPaymentAmount));
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().minus(downPaymentAmount));
loanApplicationTerms.resetFixedEmiAmount();
return installment;
}
@@ -345,8 +324,9 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement,
LoanScheduleParams scheduleParams, final Collection<LoanScheduleModelPeriod> periods, final LocalDate scheduledDueDate) {
for (Map.Entry<LocalDate, Money> disburseDetail : scheduleParams.getDisburseDetailMap().entrySet()) {
if (disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate())
&& !disburseDetail.getKey().isAfter(scheduledDueDate)) {
if ((disburseDetail.getKey().isEqual(scheduleParams.getPeriodStartDate())
|| disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()))
&& disburseDetail.getKey().isBefore(scheduledDueDate)) {
// validation check for amount not exceeds specified max
// amount as per the configuration
loanApplicationTerms.getMaxOutstandingBalance();
@@ -378,11 +358,10 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
}
// updates actual outstanding balance with new
// disbursement detail
Money remainingPrincipal = disburseDetail.getValue().minus(downPaymentAmt);
scheduleParams.addOutstandingBalance(remainingPrincipal);
scheduleParams.addOutstandingBalanceAsPerRest(remainingPrincipal);
scheduleParams.addPrincipalToBeScheduled(remainingPrincipal);
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(remainingPrincipal));
scheduleParams.addOutstandingBalance(disburseDetail.getValue());
scheduleParams.addOutstandingBalanceAsPerRest(disburseDetail.getValue());
scheduleParams.addPrincipalToBeScheduled(disburseDetail.getValue());
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseDetail.getValue()));
loanApplicationTerms.resetFixedEmiAmount();
}
}
@@ -585,43 +564,6 @@ public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanSc
return interestCharges;
}
private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms, LocalDate disbursementDate,
final Map<LocalDate, Money> disburseDetails, final boolean excludePastUnDisbursed) {
// this method relates to multi-disbursement loans
BigDecimal principal = BigDecimal.ZERO;
if (loanApplicationTerms.getDisbursementDatas().size() == 0) {
// non tranche loans have no disbursement data entries in submitted and approved status
// the appropriate approved amount or applied for amount is used to show a proposed schedule
if (loanApplicationTerms.getApprovedPrincipal().getAmount().compareTo(BigDecimal.ZERO) > 0) {
principal = loanApplicationTerms.getApprovedPrincipal().getAmount();
} else {
principal = loanApplicationTerms.getPrincipal().getAmount();
}
} else {
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
if (disbursementData.disbursementDate().equals(disbursementDate)) {
principal = principal.add(disbursementData.getPrincipal());
} else if (!excludePastUnDisbursed || disbursementData.isDisbursed()
|| !disbursementData.disbursementDate().isBefore(DateUtils.getBusinessLocalDate())) {
/*
* JW: sums up amounts by disbursal date in case of side-effect issues. Original assumed that there
* were no duplicate disbursal dates and 'put' each amount into the map keyed by date
*/
Money previousSum = disburseDetails.get(disbursementData.disbursementDate());
BigDecimal sumToNow = BigDecimal.ZERO;
if (previousSum != null) {
sumToNow = previousSum.getAmount();
}
sumToNow = sumToNow.add(disbursementData.getPrincipal());
disburseDetails.put(disbursementData.disbursementDate(), Money.of(currency, sumToNow));
}
}
}
return principal;
}
private void updateOutstandingBalance(LoanScheduleParams scheduleParams, ScheduleCurrentPeriodParams currentPeriodParams) {
// update outstandingLoanBlance using current period
// 'principalDue'
@@ -20,13 +20,9 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
import org.springframework.stereotype.Component;
@Component
@@ -47,21 +43,30 @@ public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanSch
}
@Override
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator,
BigDecimal interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest,
Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance,
LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap<LocalDate, Money> principalVariation,
Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
Collection<LoanTermVariationsData> termVariations) {
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms,
LoanScheduleParams loanScheduleParams, MathContext mc) {
// TODO: handle interest calculation
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance,
periodNumber, mc, Money.zero(loanApplicationTerms.getCurrency()));
final Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment);
int periodNumber = loanScheduleParams.getPeriodNumber();
BigDecimal fixedEMIAmount = loanApplicationTerms.getFixedEmiAmount();
Money calculatedPrincipal;
if (fixedEMIAmount == null) {
int noRemainingPeriod = loanApplicationTerms.getActualNoOfRepaymnets() - periodNumber + 1;
calculatedPrincipal = loanScheduleParams.getOutstandingBalance().dividedBy(noRemainingPeriod, mc.getRoundingMode());
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal,
loanApplicationTerms.getInstallmentAmountInMultiplesOf());
}
loanApplicationTerms.setFixedEmiAmount(calculatedPrincipal.getAmount());
} else {
calculatedPrincipal = Money.of(loanApplicationTerms.getCurrency(), fixedEMIAmount);
}
// adjust if needed
principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
totalCumulativePrincipalToDate, periodNumber);
if (periodNumber == loanApplicationTerms.getActualNoOfRepaymnets()) {
Money remainingAmount = loanScheduleParams.getOutstandingBalance().minus(calculatedPrincipal);
calculatedPrincipal = calculatedPrincipal.plus(remainingAmount);
}
return new PrincipalInterest(principalForThisInstallment, Money.zero(loanApplicationTerms.getCurrency()),
return new PrincipalInterest(calculatedPrincipal, Money.zero(loanApplicationTerms.getCurrency()),
Money.zero(loanApplicationTerms.getCurrency()));
}
}
@@ -23,13 +23,14 @@ import java.util.Map;
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
public interface LoanScheduleHistoryReadPlatformService {
Integer fetchCurrentVersionNumber(Long loanId);
LoanScheduleData retrieveRepaymentArchiveSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
Collection<DisbursementData> disbursementData);
Collection<DisbursementData> disbursementData, LoanScheduleType loanScheduleType);
Map<String, Object> fetchOldAuditDates(Long id);
}
@@ -37,6 +37,7 @@ import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLo
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -68,7 +69,8 @@ public class LoanScheduleHistoryReadPlatformServiceImpl implements LoanScheduleH
@Override
public LoanScheduleData retrieveRepaymentArchiveSchedule(final Long loanId,
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData) {
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData,
LoanScheduleType loanScheduleType) {
try {
this.context.authenticatedUser();
@@ -77,7 +79,7 @@ public class LoanScheduleHistoryReadPlatformServiceImpl implements LoanScheduleH
return null;
}
final LoanScheduleArchiveResultSetExtractor fullResultsetExtractor = new LoanScheduleArchiveResultSetExtractor(
repaymentScheduleRelatedLoanData, disbursementData);
repaymentScheduleRelatedLoanData, disbursementData, loanScheduleType);
final String sql = "select " + fullResultsetExtractor.schema()
+ " where ls.loan_id = ? and ls.version = ? order by ls.loan_id, ls.installment";
@@ -99,17 +101,19 @@ public class LoanScheduleHistoryReadPlatformServiceImpl implements LoanScheduleH
private final DisbursementData disbursement;
private final BigDecimal totalFeeChargesDueAtDisbursement;
private final Collection<DisbursementData> disbursementData;
private final LoanScheduleType loanScheduleType;
private LocalDate lastDueDate;
private BigDecimal outstandingLoanPrincipalBalance;
LoanScheduleArchiveResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
Collection<DisbursementData> disbursementData) {
Collection<DisbursementData> disbursementData, LoanScheduleType loanScheduleType) {
this.currency = repaymentScheduleRelatedLoanData.getCurrency();
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
this.lastDueDate = this.disbursement.disbursementDate();
this.outstandingLoanPrincipalBalance = this.disbursement.getPrincipal();
this.disbursementData = disbursementData;
this.loanScheduleType = loanScheduleType;
}
public String schema() {
@@ -163,7 +167,7 @@ public class LoanScheduleHistoryReadPlatformServiceImpl implements LoanScheduleH
data.getPrincipal(), this.totalFeeChargesDueAtDisbursement, data.isDisbursed());
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.getPrincipal());
} else if (data.isDueForDisbursement(fromDate, dueDate)
} else if (data.isDueForDisbursement(loanScheduleType, fromDate, dueDate)
&& this.outstandingLoanPrincipalBalance.compareTo(BigDecimal.ZERO) > 0) {
principal = principal.add(data.getPrincipal());
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(),
@@ -18,7 +18,6 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
@@ -43,6 +42,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
public interface LoanReadPlatformService {
@@ -51,7 +51,7 @@ public interface LoanReadPlatformService {
LoanAccountData fetchRepaymentScheduleData(LoanAccountData accountData);
LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData,
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges);
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType);
Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
@@ -245,7 +245,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final Collection<DisbursementData> disbursementData = retrieveLoanDisbursementDetails(accountData.getId());
final LoanScheduleData repaymentSchedule = retrieveRepaymentSchedule(accountData.getId(), repaymentScheduleRelatedData,
disbursementData, accountData.isInterestRecalculationEnabled(),
accountData.getSummary() != null ? accountData.getSummary().getFeeChargesPaid() : BigDecimal.ZERO);
LoanScheduleType.fromEnumOptionData(accountData.getLoanScheduleType()));
accountData.setRepaymentSchedule(repaymentSchedule);
return accountData;
}
@@ -253,13 +253,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
@Override
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData,
boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges) {
boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) {
try {
this.context.authenticatedUser();
final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor(
repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled);
repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled, loanScheduleType);
final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment";
return this.jdbcTemplate.query(sql, fullResultsetExtractor, loanId); // NOSONAR
@@ -1123,19 +1123,21 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
private final DisbursementData disbursement;
private final BigDecimal totalFeeChargesDueAtDisbursement;
private final Collection<DisbursementData> disbursementData;
private final LoanScheduleType loanScheduleType;
private LocalDate lastDueDate;
private BigDecimal outstandingLoanPrincipalBalance;
private boolean excludePastUndisbursed;
private boolean excludePastUnDisbursed;
LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled) {
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) {
this.currency = repaymentScheduleRelatedLoanData.getCurrency();
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
this.lastDueDate = this.disbursement.disbursementDate();
this.outstandingLoanPrincipalBalance = this.disbursement.getPrincipal();
this.disbursementData = disbursementData;
this.excludePastUndisbursed = isInterestRecalculationEnabled;
this.excludePastUnDisbursed = isInterestRecalculationEnabled;
this.loanScheduleType = loanScheduleType;
}
public String schema() {
@@ -1160,7 +1162,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
this.disbursement.disbursementDate(), this.disbursement.getPrincipal(), this.totalFeeChargesDueAtDisbursement,
this.disbursement.isDisbursed());
final Collection<LoanSchedulePeriodData> periods = new ArrayList<>();
final List<LoanSchedulePeriodData> periods = new ArrayList<>();
final MonetaryCurrency monCurrency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces(),
this.currency.getInMultiplesOf());
BigDecimal totalPrincipalDisbursed = BigDecimal.ZERO;
@@ -1170,7 +1172,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
totalPrincipalDisbursed = Money.of(monCurrency, this.disbursement.getPrincipal()).getAmount();
} else {
if (!this.disbursement.isDisbursed()) {
excludePastUndisbursed = false;
excludePastUnDisbursed = false;
}
for (DisbursementData data : disbursementData) {
if (data.getChargeAmount() != null) {
@@ -1211,56 +1213,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate");
final boolean complete = rs.getBoolean("complete");
final boolean isAdditional = rs.getBoolean("isAdditional");
BigDecimal principal = BigDecimal.ZERO;
BigDecimal disbursedAmount = BigDecimal.ZERO;
if (!isAdditional) {
for (final DisbursementData data : disbursementData) {
if (fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate)
&& !disbursementPeriodIds.contains(data.getId())) {
principal = principal.add(data.getPrincipal());
LoanSchedulePeriodData periodData = null;
if (data.getChargeAmount() == null) {
periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.getPrincipal(),
disbursementChargeAmount, data.isDisbursed());
} else {
periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.getPrincipal(),
disbursementChargeAmount.add(data.getChargeAmount()).subtract(waivedChargeAmount),
data.isDisbursed());
}
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.getPrincipal());
disbursementPeriodIds.add(data.getId());
} else if (data.isDueForDisbursement(fromDate, dueDate) && !disbursementPeriodIds.contains(data.getId())) {
if (!excludePastUndisbursed || data.isDisbursed() || !DateUtils.isBeforeBusinessDate(data.disbursementDate())) {
principal = principal.add(data.getPrincipal());
LoanSchedulePeriodData periodData;
if (data.getChargeAmount() == null) {
periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.getPrincipal(),
BigDecimal.ZERO, data.isDisbursed());
} else {
periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.getPrincipal(),
data.getChargeAmount(), data.isDisbursed());
}
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.getPrincipal());
disbursementPeriodIds.add(data.getId());
}
} else if (fromDate.equals(dueDate) && data.disbursementDate().equals(fromDate)
&& !disbursementPeriodIds.contains(data.getId())) {
principal = principal.add(data.getPrincipal());
LoanSchedulePeriodData periodData = null;
if (data.getChargeAmount() == null) {
periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.getPrincipal(),
disbursementChargeAmount, data.isDisbursed());
} else {
periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.getPrincipal(),
disbursementChargeAmount.add(data.getChargeAmount()).subtract(waivedChargeAmount),
data.isDisbursed());
}
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.getPrincipal());
disbursementPeriodIds.add(data.getId());
}
}
disbursedAmount = processDisbursementData(loanScheduleType, disbursementData, fromDate, dueDate, disbursementPeriodIds,
disbursementChargeAmount, waivedChargeAmount, periods);
}
// Add the Charge back or Credits to the initial amount to avoid negative balance
final BigDecimal credits = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalCredits");
@@ -1268,7 +1225,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(credits);
}
totalPrincipalDisbursed = totalPrincipalDisbursed.add(principal);
totalPrincipalDisbursed = totalPrincipalDisbursed.add(disbursedAmount);
Integer daysInPeriod = 0;
if (fromDate != null) {
@@ -1379,6 +1336,39 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
totalPaidLate.getAmount(), totalOutstanding.getAmount(), totalCredits.getAmount());
}
private BigDecimal processDisbursementData(LoanScheduleType loanScheduleType, Collection<DisbursementData> disbursementData,
LocalDate fromDate, LocalDate dueDate, Set<Long> disbursementPeriodIds, BigDecimal disbursementChargeAmount,
BigDecimal waivedChargeAmount, List<LoanSchedulePeriodData> periods) {
BigDecimal disbursedAmount = BigDecimal.ZERO;
for (final DisbursementData data : disbursementData) {
boolean isDueForDisbursement = data.isDueForDisbursement(loanScheduleType, fromDate, dueDate);
if (((fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate))
|| (fromDate.equals(dueDate) && data.disbursementDate().equals(fromDate))
|| canAddDisbursementData(data, isDueForDisbursement, excludePastUnDisbursed))
&& !disbursementPeriodIds.contains(data.getId())) {
disbursedAmount = disbursedAmount.add(data.getPrincipal());
LoanSchedulePeriodData periodData = createLoanSchedulePeriodData(data, disbursementChargeAmount, waivedChargeAmount);
periods.add(periodData);
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(periodData.getPrincipalDisbursed());
disbursementPeriodIds.add(data.getId());
}
}
return disbursedAmount;
}
private LoanSchedulePeriodData createLoanSchedulePeriodData(final DisbursementData data, BigDecimal disbursementChargeAmount,
BigDecimal waivedChargeAmount) {
BigDecimal chargeAmount = data.getChargeAmount() == null ? disbursementChargeAmount
: disbursementChargeAmount.add(data.getChargeAmount()).subtract(waivedChargeAmount);
return LoanSchedulePeriodData.disbursementOnlyPeriod(data.disbursementDate(), data.getPrincipal(), chargeAmount,
data.isDisbursed());
}
private boolean canAddDisbursementData(DisbursementData data, boolean isDueForDisbursement, boolean excludePastUnDisbursed) {
return (!excludePastUnDisbursed || data.isDisbursed() || !DateUtils.isBeforeBusinessDate(data.disbursementDate()))
&& isDueForDisbursement;
}
}
private static final class LoanTransactionsMapper implements RowMapper<LoanTransactionData> {