diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java index 243426ddd..53e318fff 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java @@ -23,6 +23,7 @@ import java.time.LocalDate; import lombok.Getter; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; /** * Immutable data object representing disbursement information. @@ -89,9 +90,12 @@ public final class DisbursementData implements Comparable { return DateUtils.compare(obj.expectedDisbursementDate, this.expectedDisbursementDate); } - public boolean isDueForDisbursement(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) { + public boolean isDueForDisbursement(LoanScheduleType loanScheduleType, final LocalDate fromDate, final LocalDate toDate) { final LocalDate dueDate = disbursementDate(); - return occursOnDayFromAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate); + return switch (loanScheduleType) { + case CUMULATIVE -> occursOnDayFromAndUpToAndIncluding(fromDate, toDate, dueDate); + case PROGRESSIVE -> occursOnDayFromAndIncludingAndUpTo(fromDate, toDate, dueDate); + }; } private boolean occursOnDayFromAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive, @@ -99,6 +103,12 @@ public final class DisbursementData implements Comparable { return DateUtils.isAfter(target, fromNotInclusive) && !DateUtils.isAfter(target, upToAndInclusive); } + private boolean occursOnDayFromAndIncludingAndUpTo(final LocalDate fromInclusive, final LocalDate upToNotInclusive, + final LocalDate target) { + return (DateUtils.isEqual(target, fromInclusive) || DateUtils.isAfter(target, fromInclusive)) + && DateUtils.isBefore(target, upToNotInclusive); + } + public BigDecimal getWaivedChargeAmount() { return this.waivedChargeAmount == null ? BigDecimal.ZERO : this.waivedChargeAmount; } 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 9994201e7..fefbd022c 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 @@ -282,7 +282,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep List installments) { final MathContext mc = MoneyHelper.getMathContext(); List candidateRepaymentInstallments = installments.stream().filter( - i -> !i.getDueDate().isBefore(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional()) + i -> i.getDueDate().isAfter(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional()) .toList(); int noCandidateRepaymentInstallments = candidateRepaymentInstallments.size(); LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail(); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleType.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleType.java index f56173377..d9a704684 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleType.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleType.java @@ -39,4 +39,11 @@ public enum LoanScheduleType { public EnumOptionData asEnumOptionData() { return new EnumOptionData((long) this.ordinal(), this.name(), this.humanReadableName); } + + public static LoanScheduleType fromEnumOptionData(EnumOptionData enumOptionData) { + if (enumOptionData == null) { + return null; + } + return valueOf(enumOptionData.getCode()); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index e379e841b..7f6da6f65 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -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); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java index d0c1ba0cb..2399093a6 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractProgressiveLoanScheduleGenerator.java @@ -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 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 principalVariation, - Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, - Collection termVariations); + public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms, + LoanScheduleParams loanScheduleParams, MathContext mc); // Private, internal methods private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set 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 periods, final LocalDate scheduledDueDate) { for (Map.Entry 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 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' diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index 99a106e2d..f05675e3c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -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 principalVariation, - Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, - Collection 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())); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java index 22270a17b..3b25c5027 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformService.java @@ -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); + Collection disbursementData, LoanScheduleType loanScheduleType); Map fetchOldAuditDates(Long id); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java index d098c63d8..8b35fef47 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleHistoryReadPlatformServiceImpl.java @@ -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) { + final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection 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; + private final LoanScheduleType loanScheduleType; private LocalDate lastDueDate; private BigDecimal outstandingLoanPrincipalBalance; LoanScheduleArchiveResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, - Collection disbursementData) { + Collection 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(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java index 235b6b87f..1b0c64e4d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -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, boolean isInterestRecalculationEnabled, BigDecimal totalPaidFeeCharges); + Collection disbursementData, boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType); Collection retrieveLoanTransactions(Long loanId); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index f0d024031..6adaa9975 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -245,7 +245,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa final Collection 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, - 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; + private final LoanScheduleType loanScheduleType; private LocalDate lastDueDate; private BigDecimal outstandingLoanPrincipalBalance; - private boolean excludePastUndisbursed; + private boolean excludePastUnDisbursed; LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, - Collection disbursementData, boolean isInterestRecalculationEnabled) { + Collection 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 periods = new ArrayList<>(); + final List 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, + LocalDate fromDate, LocalDate dueDate, Set disbursementPeriodIds, BigDecimal disbursementChargeAmount, + BigDecimal waivedChargeAmount, List 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 { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index 64c51a6c0..e587e126d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -57,7 +57,6 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CommonConstants; -import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; @@ -78,7 +77,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ExtendWith(LoanTestLifecycleExtension.class) -public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { +public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoanIntegrationTest { private static final Logger LOG = LoggerFactory.getLogger(AdvancedPaymentAllocationLoanRepaymentScheduleTest.class); private static final String DATETIME_PATTERN = "dd MMMM yyyy"; @@ -122,10 +121,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { @Test public void uc1() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -182,9 +178,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 0.0, 0.0); validateLoanTransaction(loanDetails, 4, 125.0, 125.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC2: Overpayment1 // ADVANCED_PAYMENT_ALLOCATION_STRATEGY @@ -195,10 +189,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { @Test public void uc2() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -255,9 +246,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 25.0, 0.0); validateLoanTransaction(loanDetails, 4, 125.0, 100.0, 25.0, 0.0); assertTrue(loanDetails.getStatus().getOverpaid()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC3: Overpayment2 // ADVANCED_PAYMENT_ALLOCATION_STRATEGY @@ -267,10 +256,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { @Test public void uc3() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -327,9 +313,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 25.0, 0.0); validateLoanTransaction(loanDetails, 4, 125.0, 100.0, 25.0, 0.0); assertTrue(loanDetails.getStatus().getOverpaid()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC4: Delinquent balance // ADVANCED_PAYMENT_ALLOCATION_STRATEGY @@ -340,10 +324,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { @Test public void uc4() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -419,9 +400,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 0.0, 0.0); validateLoanTransaction(loanDetails, 5, 360.0, 360.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC5: Refund past due @@ -432,10 +411,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay rest on time @Test public void uc5() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -507,9 +483,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 58.34, 0.0); validateLoanTransaction(loanDetails, 5, 66.66, 66.66, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC7: Refund & due reamortization @@ -521,10 +495,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay rest on time @Test public void uc7() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -594,9 +565,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 100.0, 0.0); validateLoanTransaction(loanDetails, 5, 25.0, 25.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC8: Refund after due & past due @@ -608,10 +577,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay rest on time @Test public void uc8() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -686,9 +652,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 25.0, 0.0); validateLoanTransaction(loanDetails, 5, 100.0, 100.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC9: Refund next installment @@ -699,10 +663,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay rest on time @Test public void uc9() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -759,9 +720,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 0.0, 0.0); validateLoanTransaction(loanDetails, 4, 125.0, 125.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC10: Refund PD and next installment @@ -773,10 +732,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay rest on time @Test public void uc10() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -848,9 +804,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 0.0, 0.0); validateLoanTransaction(loanDetails, 5, 125.0, 125.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC11: Refund Past, pay in advance installments @@ -861,10 +815,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay rest on time @Test public void uc11() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -913,9 +864,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 25.0, 0.0); validateLoanTransaction(loanDetails, 3, 100.0, 100.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC12: Refund last installment @@ -926,10 +875,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay rest on time @Test public void uc12() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -986,9 +932,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 125.0, 0.0); validateLoanTransaction(loanDetails, 3, 125.0, 125.0, 0.0, 50.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC13: Due apply last installment @@ -1000,10 +944,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 5. Pay rest on time @Test public void uc13() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1074,9 +1015,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 75.0, 0.0); validateLoanTransaction(loanDetails, 5, 50.0, 50.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC14: Refund PD @@ -1088,10 +1027,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 5. Pay rest on time @Test public void uc14() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1164,9 +1100,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 0.0, 0.0); validateLoanTransaction(loanDetails, 5, 175.0, 175.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC15: Goodwill credit PD @@ -1178,10 +1112,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 5. Pay rest on time @Test public void uc15() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1251,9 +1182,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 75.0, 0.0); validateLoanTransaction(loanDetails, 5, 50.0, 50.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC17a: Full refund with CBR @@ -1265,10 +1194,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 5. CBR @Test public void uc17a() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1312,9 +1238,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 125.0, 0.0); validateLoanTransaction(loanDetails, 3, 125.0, 0.0, 125.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC17b: Full refund with CBR @@ -1326,10 +1250,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 5. CBR @Test public void uc17b() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1373,9 +1294,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 125.0, 0.0); validateLoanTransaction(loanDetails, 3, 125.0, 0.0, 125.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC17c: Full refund with CBR @@ -1387,10 +1306,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 5. CBR @Test public void uc17c() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.15").dateFormat("yyyy.MM.dd").locale("en")); + runAt("15 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1434,9 +1350,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 125.0, 0.0); validateLoanTransaction(loanDetails, 3, 125.0, 0.0, 125.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC18: Full refund with CBR (N+1) @@ -1448,10 +1362,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 5. CBR @Test public void uc18() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.20").dateFormat("yyyy.MM.dd").locale("en")); + runAt("20 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1530,9 +1441,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 0.0, 0.0); validateLoanTransaction(loanDetails, 6, 500.0, 0.0, 500.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC24: Merchant issued credit reverse-replay @@ -1546,10 +1455,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 7. Payments @Test public void uc24() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.20").dateFormat("yyyy.MM.dd").locale("en")); + runAt("20 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1651,9 +1557,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 125.0, 0.0, 0.0, 0.0); validateLoanTransaction(loanDetails, 7, 125.0, 125.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC25: Merchant issued credit reverse-replay with uneven balances @@ -1665,10 +1569,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 5. Merchant issued credit @Test public void uc25() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.20").dateFormat("yyyy.MM.dd").locale("en")); + runAt("20 February 2023", () -> { final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), commonLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); @@ -1725,9 +1626,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, 125.0, 115.0, 10.0, 115.0, 0.0); validateLoanTransaction(loanDetails, 4, 40.0, 40.0, 0.0, 10.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC101: Multiple disbursement test @@ -1737,11 +1636,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 3. Disburse again @Test public void uc101() { - try { - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.20").dateFormat("yyyy.MM.dd").locale("en")); + runAt("20 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -1808,9 +1703,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2023, 2, 22), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC102: Multiple disbursement & reschedule test @@ -1822,11 +1715,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Reschedule the 3rd period to be later than the 2nd disbursement @Test public void uc102() { - try { - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.20").dateFormat("yyyy.MM.dd").locale("en")); + runAt("20 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -1907,9 +1796,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 25), 218.75, 100.0, 118.75, 100.0, 0.0); validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 3, 12), 218.75, 0.0, 218.75, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC103: Advanced payment allocation, add charge after maturity -> N+1 installment, repay the loan @@ -1919,11 +1806,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 3. Fully repay the loan @Test public void uc103() { - try { - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -1978,9 +1861,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2023, 2, 22), 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getClosedObligationsMet()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC104: Advanced payment allocation, horizontal repayment processing @@ -1991,11 +1872,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay little more than the principal of 1st installment and charge of 2nd installment @Test public void uc104() { - try { - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2063,9 +1940,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 125.0, 0.0, 125.0, 0.0, 0.0, 0.0, 25.0, 0.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC105: Advanced payment allocation, vertical repayment processing @@ -2076,11 +1951,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 4. Pay little more than the charges @Test public void uc105() { - try { - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2148,9 +2019,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 125.0, 0.0, 125.0, 0.0, 0.0, 0.0, 25.0, 25.0, 0.0, 0.0, 0.0, 0.0, 0.0, 25.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC106: Advanced payment allocation, horizontal repayment processing, mixed grouping of allocation rules @@ -2158,11 +2027,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 1. Create a Loan product, but the allocation rules are mixed -> expect validation error @Test public void uc106() { - try { - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2176,9 +2041,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { assertNotNull(loanProductErrorData); assertEquals("mixed.due.type.allocation.rules.are.not.supported.with.horizontal.installment.processing", loanProductErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC107: Advanced payment allocation, vertical repayment processing, mixed grouping of allocation rules @@ -2188,11 +2051,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 3. Change to HORIZONTAL loan schedule processing and allocation rules are mixed - > expect validation error @Test public void uc107() { - try { - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2220,9 +2079,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { assertEquals(400, exception.getResponse().code()); assertTrue(exception.getMessage() .contains("mixed.due.type.allocation.rules.are.not.supported.with.horizontal.installment.processing")); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC108: Advanced payment allocation, progressive loan schedule handling, rounding test @@ -2231,10 +2088,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 2. Submit the loan, and check the generated repayment schedule @Test public void uc108() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2313,9 +2167,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2023, 4, 1), 166.65, 0.0, 166.65, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC109: Advanced payment allocation, cumulative loan schedule handling, rounding test @@ -2324,10 +2176,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 2. Submit the loan, and check the generated repayment schedule @Test public void uc109() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2406,9 +2255,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 6, LocalDate.of(2023, 4, 1), 166.65, 0.0, 166.65, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC110: Advanced payment allocation, progressive loan schedule handling, rounding test @@ -2417,10 +2264,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 2. Submit the loan, and check the generated repayment schedule @Test public void uc110() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2462,9 +2306,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 10.13, 0.0, 10.13, 0.0, 0.0); validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 10.12, 0.0, 10.12, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC111: Advanced payment allocation, cumulative loan schedule handling, rounding test @@ -2473,10 +2315,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 2. Submit the loan, and check the generated repayment schedule @Test public void uc111() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.02.22").dateFormat("yyyy.MM.dd").locale("en")); + runAt("22 February 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2518,9 +2357,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 10.13, 0.0, 10.13, 0.0, 0.0); validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 10.12, 0.0, 10.12, 0.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC112: Advanced payment allocation, horizontal repayment processing @@ -2534,10 +2371,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 7. Do goodwill credit (in advance payment) @Test public void uc112() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en")); + runAt("01 September 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2629,9 +2463,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 20.0, 0.0); validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 20.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC113: Advanced payment allocation, vertical repayment processing @@ -2645,10 +2477,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 7. Do goodwill credit (in advance payment) @Test public void uc113() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en")); + runAt("01 September 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2739,9 +2568,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 20.0, 0.0); validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 20.0, 0.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC114: Advanced payment allocation, horizontal repayment processing @@ -2755,10 +2582,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 7. Do merchant issued refund (in advance payment) @Test public void uc114() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en")); + runAt("01 September 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2849,9 +2673,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 10.0, 10.0); validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 10.0, 10.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); } // UC115: Advanced payment allocation, vertical repayment processing @@ -2865,10 +2687,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { // 7. Do merchant issued refund (in advance payment) @Test public void uc115() { - try { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - businessDateHelper.updateBusinessDate(new BusinessDateRequest().type(BusinessDateType.BUSINESS_DATE.getName()) - .date("2023.09.01").dateFormat("yyyy.MM.dd").locale("en")); + runAt("01 September 2023", () -> { final Account assetAccount = accountHelper.createAssetAccount(); final Account incomeAccount = accountHelper.createIncomeAccount(); @@ -2957,9 +2776,239 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { validateLoanCharge(loanDetails, 1, LocalDate.of(2023, 10, 16), 20.0, 10.0, 10.0); validateLoanCharge(loanDetails, 2, LocalDate.of(2023, 10, 17), 20.0, 10.0, 10.0); assertTrue(loanDetails.getStatus().getActive()); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } + }); + } + + // UC116: Advanced payment allocation, horizontal repayment processing, disbursement on due date of an installment + // ADVANCED_PAYMENT_ALLOCATION_STRATEGY + // 1. Disburse the loan (1000) + // 2. Add charge on disbursement date(50) + // 3. Repay down payment fully on disbursement date (250) + // 4. Disburse on 1st installment due date (400) + @Test + public void uc116() { + runAt("01 January 2023", () -> { + final Account assetAccount = accountHelper.createAssetAccount(); + final Account incomeAccount = accountHelper.createIncomeAccount(); + final Account expenseAccount = accountHelper.createExpenseAccount(); + final Account overpaymentAccount = accountHelper.createLiabilityAccount(); + Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, + LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, + BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + + loanTransactionHelper.approveLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("01 January 2023").locale("en")); + + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null); + validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 250.0, 0.0, 250.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + + // Add Charge Fee + Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false)); + loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(), + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee), "01 January 2023", "50")); + + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 1050.0, 0.0, 1000.0, 0.0, null); + validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), 250.0, 0.0, 250.0, 50.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 250.0, 0.0, 250.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + + loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest() + .dateFormat(DATETIME_PATTERN).transactionDate("01 January 2023").locale("en").transactionAmount(250.0)); + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 800.0, 250.0, 750.0, 250.0, null); + validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 250.0, 250.0, 0.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), 250.0, 0.0, 250.0, 50.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 250.0, 0.0, 250.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + + updateBusinessDate("16 January 2023"); + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("16 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(400.0)).locale("en")); + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 1200.0, 250.0, 1150.0, 250.0, null); + validatePeriod(loanDetails, 0, LocalDate.of(2023, 1, 1), null, 1000.0, null, null, null, 0.0, 0.0, null, null, null, null, null, + null, null, null, null); + validatePeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), LocalDate.of(2023, 1, 1), 750.0, 250.0, 250.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), null, 500.0, 250.0, 0.0, 250.0, 50.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 3, LocalDate.of(2023, 1, 16), null, 400.0, null, null, null, 0.0, 0.0, null, null, null, null, null, + null, null, null, null); + validatePeriod(loanDetails, 4, LocalDate.of(2023, 1, 16), null, 800.0, 100.0, 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 5, LocalDate.of(2023, 1, 31), null, 400.0, 400.0, 0.0, 400.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 6, LocalDate.of(2023, 2, 15), null, 0.0, 400.0, 0.0, 400.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + }); + } + + // UC117: Advanced payment allocation, horizontal repayment processing, multi disbursement on due date of an + // installment + // ADVANCED_PAYMENT_ALLOCATION_STRATEGY + // 1. Disburse the loan (1000) + // 2. Add charge on disbursement date(50) + // 3. Repay down payment fully on disbursement date (250) + // 4. Disburse on 1st installment due date (400) + // 4. Disburse on 1st installment due date (80) + @Test + public void uc117() { + runAt("01 January 2023", () -> { + final Account assetAccount = accountHelper.createAssetAccount(); + final Account incomeAccount = accountHelper.createIncomeAccount(); + final Account expenseAccount = accountHelper.createExpenseAccount(); + final Account overpaymentAccount = accountHelper.createLiabilityAccount(); + Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, + LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, + BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + + loanTransactionHelper.approveLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("01 January 2023").locale("en")); + + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null); + validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 250.0, 0.0, 250.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + + // Add Charge Fee + Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "50", false)); + loanTransactionHelper.addChargesForLoan(loanResponse.getLoanId().intValue(), + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee), "01 January 2023", "50")); + + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 1050.0, 0.0, 1000.0, 0.0, null); + validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), 250.0, 0.0, 250.0, 50.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 250.0, 0.0, 250.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + + loanTransactionHelper.makeLoanRepayment(loanResponse.getLoanId(), new PostLoansLoanIdTransactionsRequest() + .dateFormat(DATETIME_PATTERN).transactionDate("01 January 2023").locale("en").transactionAmount(250.0)); + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 800.0, 250.0, 750.0, 250.0, null); + validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 250.0, 250.0, 0.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), 250.0, 0.0, 250.0, 50.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 250.0, 0.0, 250.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + + updateBusinessDate("16 January 2023"); + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("16 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(400.0)).locale("en")); + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("16 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(80.0)).locale("en")); + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 1280.0, 250.0, 1230.0, 250.0, null); + validatePeriod(loanDetails, 0, LocalDate.of(2023, 1, 1), null, 1000.0, null, null, null, 0.0, 0.0, null, null, null, null, null, + null, null, null, null); + validatePeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), LocalDate.of(2023, 1, 1), 750.0, 250.0, 250.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), null, 500.0, 250.0, 0.0, 250.0, 50.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 3, LocalDate.of(2023, 1, 16), null, 400.0, null, null, null, 0.0, 0.0, null, null, null, null, null, + null, null, null, null); + validatePeriod(loanDetails, 4, LocalDate.of(2023, 1, 16), null, 80.0, null, null, null, 0.0, 0.0, null, null, null, null, null, + null, null, null, null); + validatePeriod(loanDetails, 5, LocalDate.of(2023, 1, 16), null, 880.0, 100.0, 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 6, LocalDate.of(2023, 1, 16), null, 860.0, 20.0, 0.0, 20.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 7, LocalDate.of(2023, 1, 31), null, 430.0, 430.0, 0.0, 430.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 8, LocalDate.of(2023, 2, 15), null, 0.0, 430.0, 0.0, 430.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + }); + } + + // UC118: Advanced payment allocation, horizontal repayment processing, multi disbursement on disbursement date + // ADVANCED_PAYMENT_ALLOCATION_STRATEGY + // 1. Disburse the loan (1000) + // 2. Disburse on disbursement date (400) + @Test + public void uc118() { + runAt("01 January 2023", () -> { + final Account assetAccount = accountHelper.createAssetAccount(); + final Account incomeAccount = accountHelper.createIncomeAccount(); + final Account expenseAccount = accountHelper.createExpenseAccount(); + final Account overpaymentAccount = accountHelper.createLiabilityAccount(); + Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE, + LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount); + final PostLoansResponse loanResponse = applyForLoanApplication(client.getClientId(), localLoanProductId, + BigDecimal.valueOf(1000.0), 45, 15, 3, 0, "01 January 2023", "01 January 2023"); + + loanTransactionHelper.approveLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000)).dateFormat(DATETIME_PATTERN) + .approvedOnDate("01 January 2023").locale("en")); + + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(1000.0)).locale("en")); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 1000.0, 0.0, 1000.0, 0.0, null); + validateRepaymentPeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 2, LocalDate.of(2023, 1, 16), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 1, 31), 250.0, 0.0, 250.0, 0.0, 0.0); + validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 2, 15), 250.0, 0.0, 250.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), + new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2023").dateFormat(DATETIME_PATTERN) + .transactionAmount(BigDecimal.valueOf(400.0)).locale("en")); + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId()); + validateLoanSummaryBalances(loanDetails, 1400.0, 0.0, 1400.0, 0.0, null); + validatePeriod(loanDetails, 0, LocalDate.of(2023, 1, 1), null, 1000.0, null, null, null, 0.0, 0.0, null, null, null, null, null, + null, null, null, null); + validatePeriod(loanDetails, 1, LocalDate.of(2023, 1, 1), null, 400.0, null, null, null, 0.0, 0.0, null, null, null, null, null, + null, null, null, null); + validatePeriod(loanDetails, 2, LocalDate.of(2023, 1, 1), null, 1150.0, 250.0, 0.0, 250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 3, LocalDate.of(2023, 1, 1), null, 1050.0, 100.0, 0.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 4, LocalDate.of(2023, 1, 16), null, 700.0, 350.0, 0.0, 350.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 5, LocalDate.of(2023, 1, 31), null, 350.0, 350.0, 0.0, 350.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0); + validatePeriod(loanDetails, 6, LocalDate.of(2023, 2, 15), null, 0.0, 350.0, 0.0, 350.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0); + assertTrue(loanDetails.getStatus().getActive()); + }); } private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment, @@ -3085,6 +3134,30 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest { return loanTransactionHelper.getLoanProductId(loanProductJSON); } + private static void validatePeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, LocalDate paidDate, + Double balanceOfLoan, Double principalDue, Double principalPaid, Double principalOutstanding, Double feeDue, Double feePaid, + Double feeOutstanding, Double penaltyDue, Double penaltyPaid, Double penaltyOutstanding, Double interestDue, + Double interestPaid, Double interestOutstanding, Double paidInAdvance, Double paidLate) { + GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().get(index); + assertEquals(dueDate, period.getDueDate()); + assertEquals(paidDate, period.getObligationsMetOnDate()); + assertEquals(balanceOfLoan, period.getPrincipalLoanBalanceOutstanding()); + assertEquals(principalDue, period.getPrincipalDue()); + assertEquals(principalPaid, period.getPrincipalPaid()); + assertEquals(principalOutstanding, period.getPrincipalOutstanding()); + assertEquals(feeDue, period.getFeeChargesDue()); + assertEquals(feePaid, period.getFeeChargesPaid()); + assertEquals(feeOutstanding, period.getFeeChargesOutstanding()); + assertEquals(penaltyDue, period.getPenaltyChargesDue()); + assertEquals(penaltyPaid, period.getPenaltyChargesPaid()); + assertEquals(penaltyOutstanding, period.getPenaltyChargesOutstanding()); + assertEquals(interestDue, period.getInterestDue()); + assertEquals(interestPaid, period.getInterestPaid()); + assertEquals(interestOutstanding, period.getInterestOutstanding()); + assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod()); + assertEquals(paidLate, period.getTotalPaidLateForPeriod()); + } + private static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue, double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) { GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream() diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index 6d8ec7b7c..469f3f339 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -498,4 +498,9 @@ public abstract class BaseLoanIntegrationTest { public static final Integer MONTHS = 2; public static final Integer YEARS = 3; } + + public void updateBusinessDate(String date) { + businessDateHelper.updateBusinessDate( + new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en")); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java index 06275fb93..ecbc6970e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountChargeOffWithAdvancedPaymentAllocationTest.java @@ -18,9 +18,6 @@ */ package org.apache.fineract.integrationtests; -import static java.lang.Boolean.FALSE; -import static java.lang.Boolean.TRUE; -import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -42,7 +39,6 @@ import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import org.apache.fineract.client.models.AdvancedPaymentData; import org.apache.fineract.client.models.AllowAttributeOverrides; -import org.apache.fineract.client.models.BusinessDateRequest; import org.apache.fineract.client.models.ChargeData; import org.apache.fineract.client.models.ChargeToGLAccountMapper; import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse; @@ -61,7 +57,6 @@ import org.apache.fineract.client.models.PostPaymentTypesRequest; import org.apache.fineract.client.models.PostPaymentTypesResponse; import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.ClientHelper; -import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.PaymentTypeHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.accounting.Account; @@ -83,7 +78,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @ExtendWith(LoanTestLifecycleExtension.class) -public class LoanAccountChargeOffWithAdvancedPaymentAllocationTest { +public class LoanAccountChargeOffWithAdvancedPaymentAllocationTest extends BaseLoanIntegrationTest { private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); private ResponseSpecification responseSpec; @@ -777,23 +772,4 @@ public class LoanAccountChargeOffWithAdvancedPaymentAllocationTest { return paymentAllocationOrder; }).toList(); } - - private void runAt(String date, Runnable runnable) { - try { - GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 42, true); - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, TRUE); - businessDateHelper.updateBusinessDate( - new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en")); - runnable.run(); - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, FALSE); - GlobalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(requestSpec, responseSpec, 42, false); - } - } - - private void updateBusinessDate(String date) { - businessDateHelper.updateBusinessDate( - new BusinessDateRequest().type(BUSINESS_DATE.getName()).date(date).dateFormat(DATETIME_PATTERN).locale("en")); - } - } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTest.java index 9a9ec44a6..34f17cf22 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTest.java @@ -724,16 +724,16 @@ public class LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTe // down payment period [1] verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(1), 1, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, LocalDate.of(2023, 3, 3), LocalDate.of(2023, 3, 3)); - // disbursement period [2] - verifyDisbursementPeriod(loanDetails.getRepaymentSchedule().getPeriods().get(2), LocalDate.of(2023, 4, 3), 200.0); - // down payment period [3] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(3), 2, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, - LocalDate.of(2023, 4, 3), LocalDate.of(2023, 4, 3)); - // regular installment [4] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(4), 3, 150.0, 0.0, 0.0, 150.0, 0.0, 0.0, 0.0, 0.0, + // regular installment [2] + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(2), 2, 75.0, 50.0, 50.0, 25.0, 0.0, 0.0, 0.0, 0.0, false, LocalDate.of(2023, 3, 3), LocalDate.of(2023, 4, 3)); + // disbursement period [3] + verifyDisbursementPeriod(loanDetails.getRepaymentSchedule().getPeriods().get(3), LocalDate.of(2023, 4, 3), 200.0); + // down payment period [4] + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(4), 3, 50.0, 0.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, false, + LocalDate.of(2023, 4, 3), LocalDate.of(2023, 4, 3)); // regular installment [5] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(5), 4, 150.0, 0.0, 0.0, 150.0, 0.0, 0.0, 0.0, 0.0, + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(5), 4, 225.0, 0.0, 0.0, 225.0, 0.0, 0.0, 0.0, 0.0, false, LocalDate.of(2023, 4, 3), LocalDate.of(2023, 5, 3)); // same day third disbursement with overlapping installment i.e same due date as regular repayment due date @@ -758,21 +758,21 @@ public class LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTe // down payment period [1] verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(1), 1, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, LocalDate.of(2023, 3, 3), LocalDate.of(2023, 3, 3)); - // disbursement period [2] - verifyDisbursementPeriod(loanDetails.getRepaymentSchedule().getPeriods().get(2), LocalDate.of(2023, 4, 3), 200.0); + // regular installment [2] + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(2), 2, 75.0, 75.0, 75.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, + LocalDate.of(2023, 3, 3), LocalDate.of(2023, 4, 3)); // disbursement period [3] verifyDisbursementPeriod(loanDetails.getRepaymentSchedule().getPeriods().get(3), LocalDate.of(2023, 4, 3), 200.0); - // down payment period [4] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(4), 2, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, - LocalDate.of(2023, 4, 3), LocalDate.of(2023, 4, 3)); + // disbursement period [4] + verifyDisbursementPeriod(loanDetails.getRepaymentSchedule().getPeriods().get(4), LocalDate.of(2023, 4, 3), 200.0); // down payment period [5] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(5), 3, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(5), 3, 50.0, 25.0, 25.0, 25.0, 0.0, 0.0, 0.0, 0.0, + false, LocalDate.of(2023, 4, 3), LocalDate.of(2023, 4, 3)); + // down payment period [6] + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(6), 4, 50.0, 0.0, 0.0, 50.0, 0.0, 0.0, 0.0, 0.0, false, LocalDate.of(2023, 4, 3), LocalDate.of(2023, 4, 3)); - // regular installment [6] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(6), 4, 225.0, 0.0, 0.0, 225.0, 0.0, 0.0, 0.0, 0.0, - false, LocalDate.of(2023, 3, 3), LocalDate.of(2023, 4, 3)); // regular installment [7] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(7), 5, 225.0, 0.0, 0.0, 225.0, 0.0, 0.0, 0.0, 0.0, + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(7), 5, 375.0, 0.0, 0.0, 375.0, 0.0, 0.0, 0.0, 0.0, false, LocalDate.of(2023, 4, 3), LocalDate.of(2023, 5, 3)); // make repayment for fully paying and verify that regular installment gets fully paid on 3rd april @@ -791,21 +791,21 @@ public class LoanAccountPaymentAllocationWithOverlappingDownPaymentInstallmentTe // down payment period [1] verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(1), 1, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, LocalDate.of(2023, 3, 3), LocalDate.of(2023, 3, 3)); - // disbursement period [2] - verifyDisbursementPeriod(loanDetails.getRepaymentSchedule().getPeriods().get(2), LocalDate.of(2023, 4, 3), 200.0); + // regular installment [2] + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(2), 2, 75.0, 75.0, 75.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, + LocalDate.of(2023, 3, 3), LocalDate.of(2023, 4, 3)); // disbursement period [3] verifyDisbursementPeriod(loanDetails.getRepaymentSchedule().getPeriods().get(3), LocalDate.of(2023, 4, 3), 200.0); - // down payment period [4] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(4), 2, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, - LocalDate.of(2023, 4, 3), LocalDate.of(2023, 4, 3)); + // disbursement period [4] + verifyDisbursementPeriod(loanDetails.getRepaymentSchedule().getPeriods().get(4), LocalDate.of(2023, 4, 3), 200.0); // down payment period [5] verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(5), 3, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, LocalDate.of(2023, 4, 3), LocalDate.of(2023, 4, 3)); - // regular installment [6] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(6), 4, 225.0, 225.0, 225.0, 0.0, 0.0, 0.0, 0.0, 0.0, - true, LocalDate.of(2023, 3, 3), LocalDate.of(2023, 4, 3)); + // down payment period [6] + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(6), 4, 50.0, 50.0, 50.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, + LocalDate.of(2023, 4, 3), LocalDate.of(2023, 4, 3)); // regular installment [7] - verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(7), 5, 225.0, 0.0, 0.0, 225.0, 0.0, 0.0, 0.0, 0.0, + verifyPeriodDetails(loanDetails.getRepaymentSchedule().getPeriods().get(7), 5, 375.0, 150.0, 150.0, 225.0, 0.0, 0.0, 0.0, 0.0, false, LocalDate.of(2023, 4, 3), LocalDate.of(2023, 5, 3)); } finally { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductRepaymentStartDateConfigurationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductRepaymentStartDateConfigurationTest.java index c13939f80..5cbf0e594 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductRepaymentStartDateConfigurationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductRepaymentStartDateConfigurationTest.java @@ -124,7 +124,7 @@ public class LoanProductRepaymentStartDateConfigurationTest { try { // Set business date - LocalDate businessDate = LocalDate.of(2023, 03, 3); + LocalDate businessDate = LocalDate.of(2023, 3, 3); GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); @@ -169,25 +169,25 @@ public class LoanProductRepaymentStartDateConfigurationTest { // first period [2023-03-03 to 2023-04-03] assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); - assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); - assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 4, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); assertEquals(333.33, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); // second period [2023-04-03 to 2023-05-03] assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); - assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); - assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(LocalDate.of(2023, 4, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); + assertEquals(LocalDate.of(2023, 5, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); assertEquals(333.33, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); // third period [2023-05-03 to 2023-06-03] assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); - assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); - assertEquals(LocalDate.of(2023, 06, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(LocalDate.of(2023, 5, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 6, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); assertEquals(333.34, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); // first disbursement on a future date (7 March 2023) - LocalDate disbursementDate = LocalDate.of(2023, 03, 7); + LocalDate disbursementDate = LocalDate.of(2023, 3, 7); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); @@ -208,25 +208,25 @@ public class LoanProductRepaymentStartDateConfigurationTest { // first period [2023-03-03 to 2023-04-03] assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); - assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); - assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 4, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); assertEquals(166.67, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); // second period [2023-04-03 to 2023-05-03] assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); - assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); - assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(LocalDate.of(2023, 4, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); + assertEquals(LocalDate.of(2023, 5, 3), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); assertEquals(166.67, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); // third period [2023-05-03 to 2023-06-03] assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); - assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); - assertEquals(LocalDate.of(2023, 06, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(LocalDate.of(2023, 5, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 6, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); assertEquals(166.66, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); // second disbursement next month (7 April 2023) - disbursementDate = LocalDate.of(2023, 04, 7); + disbursementDate = LocalDate.of(2023, 4, 7); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); @@ -248,20 +248,20 @@ public class LoanProductRepaymentStartDateConfigurationTest { // first period [2023-03-03 to 2023-04-03] assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); - assertEquals(LocalDate.of(2023, 03, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); - assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 4, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); assertEquals(166.67, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); // second period [2023-04-03 to 2023-05-03] assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); - assertEquals(LocalDate.of(2023, 04, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); - assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(LocalDate.of(2023, 4, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 5, 3), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); assertEquals(333.33, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); // third period [2023-05-03 to 2023-06-03] assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); - assertEquals(LocalDate.of(2023, 05, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); - assertEquals(LocalDate.of(2023, 06, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(LocalDate.of(2023, 5, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); + assertEquals(LocalDate.of(2023, 6, 3), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); assertEquals(500.00, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); } finally { @@ -278,7 +278,7 @@ public class LoanProductRepaymentStartDateConfigurationTest { try { // Set business date - LocalDate businessDate = LocalDate.of(2023, 03, 3); + LocalDate businessDate = LocalDate.of(2023, 3, 3); GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, businessDate); @@ -324,25 +324,25 @@ public class LoanProductRepaymentStartDateConfigurationTest { // first period [2023-03-07 to 2023-04-07] assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); - assertEquals(LocalDate.of(2023, 03, 7), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); - assertEquals(LocalDate.of(2023, 04, 7), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(LocalDate.of(2023, 3, 7), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 4, 7), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); assertEquals(333.33, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); // second period [2023-04-07 to 2023-05-07] assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); - assertEquals(LocalDate.of(2023, 04, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); - assertEquals(LocalDate.of(2023, 05, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(LocalDate.of(2023, 4, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); + assertEquals(LocalDate.of(2023, 5, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); assertEquals(333.33, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); // third period [2023-05-07 to 2023-06-07] assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); - assertEquals(LocalDate.of(2023, 05, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); - assertEquals(LocalDate.of(2023, 06, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(LocalDate.of(2023, 5, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 6, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); assertEquals(333.34, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); // first disbursement (7 March 2023) - LocalDate disbursementDate = LocalDate.of(2023, 03, 7); + LocalDate disbursementDate = LocalDate.of(2023, 3, 7); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); @@ -364,25 +364,25 @@ public class LoanProductRepaymentStartDateConfigurationTest { // first period [2023-03-07 to 2023-04-07] assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); - assertEquals(LocalDate.of(2023, 03, 7), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); - assertEquals(LocalDate.of(2023, 04, 7), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(LocalDate.of(2023, 3, 7), loanDetails.getRepaymentSchedule().getPeriods().get(1).getFromDate()); + assertEquals(LocalDate.of(2023, 4, 7), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); assertEquals(166.67, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); // second period [2023-04-07 to 2023-05-07] assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); - assertEquals(LocalDate.of(2023, 04, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); - assertEquals(LocalDate.of(2023, 05, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(LocalDate.of(2023, 4, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); + assertEquals(LocalDate.of(2023, 5, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); assertEquals(166.67, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); // third period [2023-05-07 to 2023-06-07] assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); - assertEquals(LocalDate.of(2023, 05, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); - assertEquals(LocalDate.of(2023, 06, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(LocalDate.of(2023, 5, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 6, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); assertEquals(166.66, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); // second disbursement next month (7 April 2023) - disbursementDate = LocalDate.of(2023, 04, 7); + disbursementDate = LocalDate.of(2023, 4, 7); BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); @@ -405,20 +405,20 @@ public class LoanProductRepaymentStartDateConfigurationTest { // first period [2023-03-07 to 2023-04-07] assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); - assertEquals(LocalDate.of(2023, 03, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); - assertEquals(LocalDate.of(2023, 04, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(LocalDate.of(2023, 3, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getFromDate()); + assertEquals(LocalDate.of(2023, 4, 7), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); assertEquals(333.33, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); // second period [2023-04-07 to 2023-05-07] assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); - assertEquals(LocalDate.of(2023, 04, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); - assertEquals(LocalDate.of(2023, 05, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(LocalDate.of(2023, 4, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getFromDate()); + assertEquals(LocalDate.of(2023, 5, 7), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); assertEquals(333.33, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); // third period [2023-05-07 to 2023-06-07] assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); - assertEquals(LocalDate.of(2023, 05, 7), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); - assertEquals(LocalDate.of(2023, 06, 7), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(LocalDate.of(2023, 5, 7), loanDetails.getRepaymentSchedule().getPeriods().get(4).getFromDate()); + assertEquals(LocalDate.of(2023, 6, 7), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); assertEquals(333.34, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); } finally {