diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java index 16cd36a8d..d5332238a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java @@ -373,7 +373,6 @@ public final class CalendarUtils { } public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date) { - final Recur recur = CalendarUtils.getICalRecur(recurringRule); if (recur == null) { return false; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 3acf93cc1..105fdbfe9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -59,6 +59,7 @@ import javax.persistence.Table; import javax.persistence.Transient; import javax.persistence.UniqueConstraint; import javax.persistence.Version; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -142,6 +143,7 @@ import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDat import org.apache.fineract.useradministration.domain.AppUser; import org.springframework.stereotype.Component; +@Slf4j @Entity @Component @Table(name = "m_loan", uniqueConstraints = { @UniqueConstraint(columnNames = { "account_no" }, name = "loan_account_no_UNIQUE"), @@ -5112,15 +5114,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { actualChanges.put(LoanApiConstants.disbursementPrincipalParameterName, command.bigDecimalValueOfParameterNamed(LoanApiConstants.disbursementPrincipalParameterName, locale)); - Collection loanDisburseDetails = this.getDisbursementDetails(); - BigDecimal setPrincipalAmount = BigDecimal.ZERO; - for (LoanDisbursementDetails details : loanDisburseDetails) { - if (details.actualDisbursementDate() != null) { - setPrincipalAmount = setPrincipalAmount.add(details.principal()); - } - } + this.loanRepaymentScheduleDetail.setPrincipal(getPrincipalAmountForRepaymentSchedule()); - this.loanRepaymentScheduleDetail.setPrincipal(setPrincipalAmount); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); } else { @@ -5141,6 +5136,25 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return changedTransactionDetail; } + public BigDecimal getPrincipalAmountForRepaymentSchedule() { + BigDecimal principalAmount = BigDecimal.ZERO; + + if (isMultiDisburmentLoan() && isDisbursed()) { + Collection loanDisburseDetails = this.getDisbursementDetails(); + for (LoanDisbursementDetails details : loanDisburseDetails) { + if (details.actualDisbursementDate() != null) { + principalAmount = principalAmount.add(details.principal()); + } + } + } else if (isApproved()) { + principalAmount = getApprovedPrincipal(); + } else { + principalAmount = getPrincipal().getAmount(); + } + + return principalAmount; + } + public BigDecimal retriveLastEmiAmount() { BigDecimal emiAmount = this.fixedEmiAmount; LocalDate startDate = this.getDisbursementDate(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 3a695c326..1bb5793e7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -1259,8 +1259,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener */ private Money getPrincipalToBeScheduled(final LoanApplicationTerms loanApplicationTerms) { Money principalToBeScheduled; - if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) { - principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal(); + if (loanApplicationTerms.isMultiDisburseLoan()) { + if (loanApplicationTerms.getTotalDisbursedAmount().isGreaterThanZero()) { + principalToBeScheduled = loanApplicationTerms.getTotalMultiDisbursedAmount(); + } else if (loanApplicationTerms.getApprovedPrincipal().isGreaterThanZero()) { + principalToBeScheduled = loanApplicationTerms.getApprovedPrincipal(); + } else { + principalToBeScheduled = loanApplicationTerms.getPrincipal(); + } } else { principalToBeScheduled = loanApplicationTerms.getPrincipal(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index 154856a4c..b4abfea6d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -20,9 +20,11 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.time.LocalDate; import java.time.temporal.ChronoUnit; +import net.fortuna.ical4j.model.Recur; import org.apache.fineract.organisation.holiday.domain.Holiday; import org.apache.fineract.organisation.holiday.service.HolidayUtil; import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO; +import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType; import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil; import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.domain.Calendar; @@ -131,10 +133,14 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { private AdjustedDateDetailsDTO recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate( final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final boolean isFirstRepayment) { - - checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms, - isFirstRepayment); - + final Recur recur = CalendarUtils.getICalRecur(holidayDetailDTO.getWorkingDays().getRecurrence()); + final boolean isSevenDaysWeek = (recur.getDayList().size() == 7); // 7 Seven days in the week + // If Workings days are not seven day week + if (!isSevenDaysWeek) { + checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms, + isFirstRepayment); + } + // Check Holidays If applied checkAndUpdateWorkingDayIfRepaymentDateIsHolidayDay(adjustedDateDetailsDTO, holidayDetailDTO, loanApplicationTerms, isFirstRepayment); @@ -148,6 +154,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { recursivelyCheckNonWorkingDaysAndHolidaysAndWorkingDaysExemptionToGenerateNextRepaymentPeriodDate(adjustedDateDetailsDTO, loanApplicationTerms, holidayDetailDTO, isFirstRepayment); } + return adjustedDateDetailsDTO; } @@ -192,10 +199,12 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { */ private void checkAndUpdateWorkingDayIfRepaymentDateIsNonWorkingDay(final AdjustedDateDetailsDTO adjustedDateDetailsDTO, final HolidayDetailDTO holidayDetailDTO, final LoanApplicationTerms loanApplicationTerms, final boolean isFirstRepayment) { + while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate())) { - if (WorkingDaysUtil - .getRepaymentRescheduleType(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate()) - .isMoveToNextRepaymentDay()) { + final RepaymentRescheduleType repaymentRescheduleType = WorkingDaysUtil + .getRepaymentRescheduleType(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getChangedScheduleDate()); + + if (repaymentRescheduleType.isMoveToNextRepaymentDay()) { while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDateDetailsDTO.getNextRepaymentPeriodDueDate()) || adjustedDateDetailsDTO.getChangedScheduleDate() diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index f0bb45fc2..bfa24b54d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -43,7 +43,6 @@ import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper; -import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; @@ -252,32 +251,6 @@ public final class LoanApplicationTerms { } - public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek, - final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, - final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance, - final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount, - final List disbursementDatas, final BigDecimal maxOutstandingBalance, final LocalDate interestChargedFromDate, - final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, - final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance, - final RecalculationFrequencyType compoundingFrequencyType, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, - final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate, - List loanTermVariations, final Integer numberOfDays, final boolean isSkipRepaymentOnFirstDayOfMonth, - final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod) { - final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - - return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek, - expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsTolerance, - loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, interestChargedFromDate, - principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance, - compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, loanPreClosureInterestCalculationStrategy, - rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations, - calendarHistoryDataWrapper, numberOfDays, isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, false, - false, null, false); - } - public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, @@ -337,93 +310,6 @@ public final class LoanApplicationTerms { isPrincipalCompoundingDisabledForOverdueLoans); } - public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, final LocalDate expectedDisbursementDate, - final LocalDate repaymentsStartingFromDate, final LocalDate calculatedRepaymentsStartingFromDate, - final Money inArrearsTolerance, final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, - final BigDecimal emiAmount, final List disbursementDatas, final BigDecimal maxOutstandingBalance, - final LocalDate interestChargedFromDate, final LoanInterestRecalculationDetails interestRecalculationDetails, - final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType, - final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, - final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, final Calendar loanCalendar, - BigDecimal approvedAmount, final BigDecimal annualNominalInterestRate, final List loanTermVariations, - Integer numberOfDays, boolean isSkipRepaymentOnFirstDayOfMonth, final HolidayDetailDTO holidayDetailDTO, - final boolean allowCompoundingOnEod) { - - final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); - final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); - final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); - final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod(); - final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); - final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); - final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail - .getInterestCalculationPeriodMethod(); - final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion(); - final Money principalMoney = loanProductRelatedDetail.getPrincipal(); - - // - final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment(); - final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail.recurringMoratoriumOnPrincipalPeriods(); - final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment(); - final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged(); - - // Interest recalculation settings - final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); - final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); - final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); - LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; - if (isInterestRecalculationEnabled) { - rescheduleStrategyMethod = interestRecalculationDetails.getRescheduleStrategyMethod(); - interestRecalculationCompoundingMethod = interestRecalculationDetails.getInterestRecalculationCompoundingMethod(); - } - final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; - final boolean isEqualAmortization = loanProductRelatedDetail.isEqualAmortization(); - return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentPeriodFrequencyType, null, null, amortizationMethod, interestMethod, interestRatePerPeriod, - interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, - allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, - graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, - disbursementDatas, maxOutstandingBalance, loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, - isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, - recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, - calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, - isSkipRepaymentOnFirstDayOfMonth, holidayDetailDTO, allowCompoundingOnEod, isEqualAmortization, false, false, null, false); - - } - - public static LoanApplicationTerms assembleFrom(final LoanApplicationTerms applicationTerms, - final List loanTermVariations) { - return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency, - applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, applicationTerms.repaymentEvery, - applicationTerms.repaymentPeriodFrequencyType, applicationTerms.nthDay, applicationTerms.weekDayType, - applicationTerms.amortizationMethod, applicationTerms.interestMethod, applicationTerms.interestRatePerPeriod, - applicationTerms.interestRatePeriodFrequencyType, applicationTerms.annualNominalInterestRate, - applicationTerms.interestCalculationPeriodMethod, applicationTerms.allowPartialPeriodInterestCalcualtion, - applicationTerms.principal, applicationTerms.expectedDisbursementDate, applicationTerms.repaymentsStartingFromDate, - applicationTerms.calculatedRepaymentsStartingFromDate, applicationTerms.principalGrace, - applicationTerms.recurringMoratoriumOnPrincipalPeriods, applicationTerms.interestPaymentGrace, - applicationTerms.interestChargingGrace, applicationTerms.interestChargedFromDate, applicationTerms.inArrearsTolerance, - applicationTerms.multiDisburseLoan, applicationTerms.actualFixedEmiAmount, applicationTerms.disbursementDatas, - applicationTerms.maxOutstandingBalance, applicationTerms.graceOnArrearsAgeing, applicationTerms.daysInMonthType, - applicationTerms.daysInYearType, applicationTerms.interestRecalculationEnabled, applicationTerms.rescheduleStrategyMethod, - applicationTerms.interestRecalculationCompoundingMethod, applicationTerms.restCalendarInstance, - applicationTerms.recalculationFrequencyType, applicationTerms.compoundingCalendarInstance, - applicationTerms.compoundingFrequencyType, applicationTerms.principalThresholdForLastInstalment, - applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy, - applicationTerms.loanCalendar, applicationTerms.approvedPrincipal.getAmount(), loanTermVariations, - applicationTerms.calendarHistoryDataWrapper, applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled, - applicationTerms.numberOfDays, applicationTerms.isSkipRepaymentOnFirstDayOfMonth, applicationTerms.holidayDetailDTO, - applicationTerms.allowCompoundingOnEod, applicationTerms.isEqualAmortization, - applicationTerms.isFirstRepaymentDateAllowedOnHoliday, applicationTerms.isInterestToBeRecoveredFirstWhenGreaterThanEMI, - applicationTerms.fixedPrincipalPercentagePerInstallment, applicationTerms.isPrincipalCompoundingDisabledForOverdueLoans); - } - private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer nthDay, final DayOfWeekType weekDayType, @@ -1702,6 +1588,18 @@ public final class LoanApplicationTerms { return disbursedAmount; } + public Money getTotalMultiDisbursedAmount() { + Money disbursedAmount = Money.zero(getCurrency()); + if (isMultiDisburseLoan()) { + for (DisbursementData disbursement : getDisbursementDatas()) { + disbursedAmount = disbursedAmount.plus(disbursement.getPrincipal()); + } + } else { + disbursedAmount = getPrincipal(); + } + return disbursedAmount; + } + public void updatePeriodNumberApplicableForPrincipalOrInterestGrace(final Integer periodsApplicationForGrace) { int applicablePeriodNumber = periodsApplicationForGrace; int graceOnPrincipal = defaultToZeroIfNull(this.principalGrace); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 58ff84cbb..19c9462b5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -2996,20 +2996,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf ChangedTransactionDetail changedTransactionDetail = null; if (command.entityId() != null) { - changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, changes, scheduleGeneratorDTO); } else { - // BigDecimal setAmount = loan.getApprovedPrincipal(); - Collection loanDisburseDetails = loan.getDisbursementDetails(); - BigDecimal setAmount = BigDecimal.ZERO; - for (LoanDisbursementDetails details : loanDisburseDetails) { - if (details.actualDisbursementDate() != null) { - setAmount = setAmount.add(details.principal()); - } - } - - loan.repaymentScheduleDetail().setPrincipal(setAmount); + loan.repaymentScheduleDetail().setPrincipal(loan.getPrincipalAmountForRepaymentSchedule()); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java index f2a61b0db..0303cf825 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java @@ -1288,7 +1288,7 @@ public class ClientLoanIntegrationTest { .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // .withExpectedDisbursementDate("20 September 2011") // .withSubmittedOnDate("20 September 2011") // - .withwithRepaymentStrategy(repaymentStrategy) // + .withRepaymentStrategy(repaymentStrategy) // .withCollaterals(collaterals).withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); } @@ -1316,7 +1316,7 @@ public class ClientLoanIntegrationTest { .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // .withExpectedDisbursementDate(fourMonthsfromNow) // .withSubmittedOnDate(fourMonthsfromNow) // - .withwithRepaymentStrategy(repaymentStrategy) // + .withRepaymentStrategy(repaymentStrategy) // .withCollaterals(collaterals).withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); } @@ -4757,7 +4757,7 @@ public class ClientLoanIntegrationTest { .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // .withExpectedDisbursementDate(disbursementDate) // .withSubmittedOnDate(disbursementDate) // - .withwithRepaymentStrategy(repaymentStrategy) // + .withRepaymentStrategy(repaymentStrategy) // .withPrincipalGrace(graceOnPrincipalPayment) // .withInterestGrace(graceOnInterestPayment)// .withCharges(charges)// @@ -5816,7 +5816,7 @@ public class ClientLoanIntegrationTest { .withRepaymentEveryAfter("2") // .withAmortizationTypeAsEqualPrincipalPayments().withRepaymentFrequencyTypeAsWeeks() - .withwithRepaymentStrategy(LoanProductTestBuilder.RBI_INDIA_STRATEGY).withInterestTypeAsFlatBalance() + .withRepaymentStrategy(LoanProductTestBuilder.RBI_INDIA_STRATEGY).withInterestTypeAsFlatBalance() .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withPrincipalGrace("1").withInterestGrace("1") .withLoanTermFrequency("4") // .withLoanTermFrequencyAsMonths() // diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java index ab5c8e10d..17814e645 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/DelinquencyBucketsIntegrationTest.java @@ -478,7 +478,7 @@ public class DelinquencyBucketsIntegrationTest { // First Loan Delinquency Classification after Disbursement command assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); - printRepaymentSchedule(getLoansLoanIdResponse); + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); // Apply a partial repayment operationDate = Utils.dateFormatter.format(todaysDate); @@ -660,7 +660,7 @@ public class DelinquencyBucketsIntegrationTest { // First Loan Delinquency Classification after Disbursement command assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected01); - printRepaymentSchedule(getLoansLoanIdResponse); + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); // Apply a repayment to get a first full paid installment operationDate = Utils.dateFormatter.format(todaysDate); @@ -752,7 +752,7 @@ public class DelinquencyBucketsIntegrationTest { // First Loan Delinquency Classification after Disbursement command assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected); - printRepaymentSchedule(getLoansLoanIdResponse); + loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); // Apply a repayment to get a full paid installment operationDate = Utils.dateFormatter.format(todaysDate); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAuditingIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAuditingIntegrationTest.java index fc2de616f..05396c2ed 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAuditingIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanAuditingIntegrationTest.java @@ -176,7 +176,7 @@ public class LoanAuditingIntegrationTest { .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // .withExpectedDisbursementDate(submittedOnDate) // .withSubmittedOnDate(submittedOnDate) // - .withwithRepaymentStrategy(repaymentStrategy) // + .withRepaymentStrategy(repaymentStrategy) // .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java index a2c3d5de0..6b6df5410 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java @@ -19,6 +19,8 @@ package org.apache.fineract.integrationtests; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; @@ -32,6 +34,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; +import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; +import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CollateralManagementHelper; import org.apache.fineract.integrationtests.common.Utils; @@ -43,17 +49,15 @@ import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +@Slf4j @SuppressWarnings({ "rawtypes", "unchecked" }) public class LoanDisbursementDetailsIntegrationTest { - private static final Logger LOG = LoggerFactory.getLogger(LoanDisbursementDetailsIntegrationTest.class); private ResponseSpecification responseSpec; private RequestSpecification requestSpec; private LoanTransactionHelper loanTransactionHelper; - private Integer loanID; + private Integer loanId; private Integer disbursementId; final String approveDate = "01 March 2014"; final String expectedDisbursementDate = "01 March 2014"; @@ -80,18 +84,18 @@ public class LoanDisbursementDetailsIntegrationTest { createTranches.add(this.loanTransactionHelper.createTrancheDetail(id, "01 September 2015", "5000")); final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014"); - LOG.info("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------{}", clientID); + log.info("---------------------------------CLIENT CREATED WITH ID---------------------------------------------------{}", clientID); final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder() .withInterestTypeAsDecliningBalance().withMoratorium("", "").withAmortizationTypeAsEqualInstallments().withTranches(true) .withInterestCalculationPeriodTypeAsRepaymentPeriod(true).build(null)); - LOG.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}", + log.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}", loanProductID); final Integer loanIDWithEmi = applyForLoanApplicationWithEmiAmount(clientID, loanProductID, proposedAmount, createTranches, installmentAmount); - LOG.info("-----------------------------------LOAN CREATED WITH EMI LOANID------------------------------------------------- {}", + log.info("-----------------------------------LOAN CREATED WITH EMI LOANID------------------------------------------------- {}", loanIDWithEmi); HashMap repaymentScheduleWithEmi = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, @@ -105,24 +109,23 @@ public class LoanDisbursementDetailsIntegrationTest { HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanIDWithEmi); LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); - LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------------------------"); + log.info("-----------------------------------APPROVE LOAN-----------------------------------------------------------"); loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount("01 June 2015", "01 June 2015", "10000", loanIDWithEmi, createTranches); LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); - LOG.info( + log.info( "-----------------------------------MULTI DISBURSAL LOAN WITH EMI APPROVED SUCCESSFULLY---------------------------------------"); final Integer loanIDWithoutEmi = applyForLoanApplicationWithEmiAmount(clientID, loanProductID, proposedAmount, createTranches, withoutInstallmentAmount); - HashMap repaymentScheduleWithoutEmi = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, - loanIDWithoutEmi, "repaymentSchedule"); + this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanIDWithoutEmi, "repaymentSchedule"); ArrayList periods1 = (ArrayList) repaymentScheduleWithEmi.get("periods"); assertEquals(15, periods1.size()); - LOG.info("-----------------------------------LOAN CREATED WITHOUT EMI LOANID------------------------------------------------- {}", + log.info("-----------------------------------LOAN CREATED WITHOUT EMI LOANID------------------------------------------------- {}", loanIDWithoutEmi); /* To be uncommented once issue MIFOSX-2006 is closed. */ @@ -131,12 +134,12 @@ public class LoanDisbursementDetailsIntegrationTest { HashMap loanStatusMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanIDWithoutEmi); LoanStatusChecker.verifyLoanIsPending(loanStatusMap); - LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------------------------"); + log.info("-----------------------------------APPROVE LOAN-----------------------------------------------------------"); loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount("01 June 2015", "01 June 2015", "10000", loanIDWithoutEmi, createTranches); LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); - LOG.info( + log.info( "-----------------------------------MULTI DISBURSAL LOAN WITHOUT EMI APPROVED SUCCESSFULLY---------------------------------------"); } @@ -205,6 +208,7 @@ public class LoanDisbursementDetailsIntegrationTest { list.add(expectedRepaymentSchedule14); for (int i = 0; i < list.size(); i++) { + log.info("values {} {} {}", i, periods.get(i), list.get(i)); this.assertRepaymentScheduleValuesWithEMI(periods.get(i), list.get(i), i); } } @@ -213,7 +217,7 @@ public class LoanDisbursementDetailsIntegrationTest { assertEquals(period.get("dueDate").toString(), expectedRepaymentSchedule.getDueDate()); assertEquals(period.get("principalLoanBalanceOutstanding"), expectedRepaymentSchedule.getPrincipalLoanBalanceOutstanding()); - LOG.info("{}", period.get("totalOriginalDueForPeriod").toString()); + log.info("{}", period.get("totalOriginalDueForPeriod").toString()); assertEquals(Float.parseFloat(period.get("totalOriginalDueForPeriod").toString()), expectedRepaymentSchedule.getTotalOriginalDueForPeriod().floatValue(), 0.0f); @@ -233,103 +237,25 @@ public class LoanDisbursementDetailsIntegrationTest { } } - /* Uncomment and modify test builder values once MIFOSX-2006 is closed. */ - /* - * private void validateRepaymentScheduleWithoutEMI(ArrayList periods){ LoanDisbursementTestBuilder - * expectedRepaymentSchedule0 = new LoanDisbursementTestBuilder( "[2015, 6, 1]", 0.0f, 0.0f, null, null, 5000.0f, - * null, null, null); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule1 = new LoanDisbursementTestBuilder( "[2015, 7, 1]", 800.0f, - * 800.0f, 50.0f, 750.0f, 4250.0f, 750.0f, 750.0f, "[2015, 6, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule2 = new LoanDisbursementTestBuilder( "[2015, 8, 1]", 800.0f, - * 800.0f, 42.5f, 757.5f, 3492.5f, 757.5f, 757.5f, "[2015, 7, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule3 = new LoanDisbursementTestBuilder( "[2015, 9, 1]", 0.0f, - * 0.0f, null, null, 5000.0f, null, null, null); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule4 = new LoanDisbursementTestBuilder( "[2015, 9, 1]", 800.0f, - * 800.0f, 34.92f, 765.08f, 7727.42f, 765.08f, 765.08f, "[2015, 8, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule5 = new LoanDisbursementTestBuilder( "[2015, 10, 1]", - * 800.0f, 800.0f, 77.27f, 722.73f, 7004.69f, 722.73f, 722.73f, "[2015, 9, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule6 = new LoanDisbursementTestBuilder( "[2015, 11, 1]", - * 800.0f, 800.0f, 70.05f, 729.95f, 6274.74f, 729.95f, 729.95f, "[2015, 10, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule7 = new LoanDisbursementTestBuilder( "[2015, 12, 1]", - * 800.0f, 800.0f, 62.75f, 737.25f, 5537.49f, 737.25f, 737.25f, "[2015, 11, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule8 = new LoanDisbursementTestBuilder( "[2016, 1, 1]", 800.0f, - * 800.0f, 55.37f, 744.63f, 4792.86f, 744.63f, 744.63f, "[2015, 12, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule9 = new LoanDisbursementTestBuilder( "[2016, 2, 1]", 800.0f, - * 800.0f, 47.93f, 752.07f, 4040.79f, 752.07f, 752.07f, "[2016, 1, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule10 = new LoanDisbursementTestBuilder( "[2016, 3, 1]", - * 800.0f, 800.0f, 40.41f, 759.59f, 3281.2f, 759.59f, 759.59f, "[2016, 2, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule11 = new LoanDisbursementTestBuilder( "[2016, 4, 1]", - * 800.0f, 800.0f, 32.81f, 767.19f, 2514.01f, 767.19f, 767.19f, "[2016, 3, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule12 = new LoanDisbursementTestBuilder( "[2016, 5, 1]", - * 800.0f, 800.0f, 25.14f, 774.86f, 1739.15f, 774.86f, 774.86f, "[2016, 4, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule13 = new LoanDisbursementTestBuilder( "[2016, 6, 1]", - * 800.0f, 800.0f, 17.39f, 782.61f, 956.54f, 782.61f, 782.61f, "[2016, 5, 1]"); - * - * LoanDisbursementTestBuilder expectedRepaymentSchedule14 = new LoanDisbursementTestBuilder( "[2016, 7, 1]", - * 966.11f, 966.11f, 9.57f, 956.54f, 0.0f, 956.54f, 956.54f, "[2016, 6, 1]"); - * - * ArrayList list = new ArrayList(); - * list.add(expectedRepaymentSchedule0); list.add(expectedRepaymentSchedule1); list.add(expectedRepaymentSchedule2); - * list.add(expectedRepaymentSchedule3); list.add(expectedRepaymentSchedule4); list.add(expectedRepaymentSchedule5); - * list.add(expectedRepaymentSchedule6); list.add(expectedRepaymentSchedule7); list.add(expectedRepaymentSchedule8); - * list.add(expectedRepaymentSchedule9); list.add(expectedRepaymentSchedule10); - * list.add(expectedRepaymentSchedule11); list.add(expectedRepaymentSchedule12); - * list.add(expectedRepaymentSchedule13); list.add(expectedRepaymentSchedule14); - * - * for (int i = 0; i < list.size(); i++) { this.assertRepaymentScheduleValuesWithoutEMI(periods.get(i), list.get(i), - * i); } } - * - * private void assertRepaymentScheduleValuesWithoutEMI(HashMap period, LoanDisbursementTestBuilder - * expectedRepaymentSchedule, int position) { - * - * assertEquals(period.get("dueDate").toString(), expectedRepaymentSchedule.getDueDate()); - * assertEquals(period.get("principalLoanBalanceOutstanding"), - * expectedRepaymentSchedule.getPrincipalLoanBalanceOutstanding()); - * assertEquals(period.get("totalOriginalDueForPeriod"), expectedRepaymentSchedule.getTotalOriginalDueForPeriod()); - * assertEquals(period.get("totalOutstandingForPeriod"), expectedRepaymentSchedule.getTotalOutstandingForPeriod()); - * - * if (position != 0 && position != 3) { - * - * assertEquals(period.get("interestOutstanding"), expectedRepaymentSchedule.getInterestOutstanding()); - * assertEquals(period.get("principalOutstanding"), expectedRepaymentSchedule.getPrincipalOutstanding()); - * assertEquals(period.get("principalDue"), expectedRepaymentSchedule.getPrincipalDue()); - * assertEquals(period.get("principalOriginalDue"), expectedRepaymentSchedule.getPrincipalOriginalDue()); - * assertEquals(period.get("fromDate").toString(), expectedRepaymentSchedule.getFromDate()); } } - */ - private Integer applyForLoanApplicationWithEmiAmount(final Integer clientID, final Integer loanProductID, final String proposedAmount, + private Integer applyForLoanApplicationWithEmiAmount(final Integer clientId, final Integer loanProductId, final String proposedAmount, List tranches, final String installmentAmount) { - LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + log.info("----------------APPLYING FOR LOAN APPLICATION"); List collaterals = new ArrayList<>(); final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec); Assertions.assertNotNull(collateralId); final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec, - clientID.toString(), collateralId); + clientId.toString(), collateralId); Assertions.assertNotNull(clientCollateralId); addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1)); - final String loanApplicationJSON = new LoanApplicationTestBuilder() - // - .withPrincipal(proposedAmount) - // - .withLoanTermFrequency("12") - // - .withLoanTermFrequencyAsMonths() - // - .withNumberOfRepayments("12").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() // + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal(proposedAmount) // + .withLoanTermFrequency("12") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("12") // + .withRepaymentEveryAfter("1") // + .withRepaymentFrequencyTypeAsMonths() // .withInterestRatePerPeriod("1") // .withExpectedDisbursementDate("01 June 2015") // .withTranches(tranches) // @@ -337,12 +263,55 @@ public class LoanDisbursementDetailsIntegrationTest { .withInterestTypeAsDecliningBalance() // .withSubmittedOnDate("01 June 2015") // .withAmortizationTypeAsEqualInstallments() // - .withCollaterals(collaterals).build(clientID.toString(), loanProductID.toString(), null); + .withCollaterals(collaterals).build(clientId.toString(), loanProductId.toString(), null); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); } + @Test + public void validateEqualInstallmentsForMultiTrancheLoan() { + List emptyTranches = new ArrayList<>(); + + final String operationDate = "01 January 2014"; + final String principal = "1000"; + final String disbursedPrincipal = "900"; + + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, operationDate); + log.info("-----------------CLIENT CREATED WITH ID------------------- {}", clientId); + + final String loanProductJSON = new LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments() // + .withInterestTypeAsDecliningBalance().withMoratorium("", "").withInterestCalculationPeriodTypeAsRepaymentPeriod(true) + .withInterestTypeAsDecliningBalance() // + .withMultiDisburse() // + .withDisallowExpectectedDisbursements(true) // + .build(null); + log.info("Product {}", loanProductJSON); + final Integer loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON); + log.info("------------------LOAN PRODUCT CREATED WITH ID----------- {}", loanProductId); + + final Integer loanId = applyForMultiTrancheLoanApplication(clientId.toString(), loanProductId.toString(), principal, operationDate); + + log.info("-------------------LOAN CREATED WITH loanId----------------- {}", loanId); + + this.loanTransactionHelper.approveLoanWithApproveAmount(operationDate, expectedDisbursementDate, principal, loanId, emptyTranches); + log.info("-------------------MULTI DISBURSAL LOAN APPROVED SUCCESSFULLY-------"); + + GetLoansLoanIdResponse getLoansLoanIdResponse = this.loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(getLoansLoanIdResponse); + + this.loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + + loanTransactionHelper.disburseLoanWithTransactionAmount(operationDate, loanId, disbursedPrincipal); + log.info("-------------------MULTI DISBURSAL LOAN DISBURSED SUCCESSFULLY-------"); + getLoansLoanIdResponse = this.loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(getLoansLoanIdResponse); + this.loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); + final Double limit = 2.0; + evaluateEqualInstallmentsForRepaymentSchedule(getLoansLoanIdResponse.getRepaymentSchedule(), limit); + log.info("-----------MULTI DISBURSAL LOAN EQUAL INSTALLMENTS SUCCESSFULLY-------"); + } + @Test public void createApproveAndValidateMultiDisburseLoan() throws ParseException { @@ -351,28 +320,29 @@ public class LoanDisbursementDetailsIntegrationTest { createTranches.add(this.loanTransactionHelper.createTrancheDetail(id, "01 March 2014", "1000")); final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014"); - LOG.info("---------------------------------CLIENT CREATED WITH ID--------------------------------------------------- {}", clientID); + log.info("---------------------------------CLIENT CREATED WITH ID--------------------------------------------------- {}", clientID); final Integer loanProductID = this.loanTransactionHelper .getLoanProductId(new LoanProductTestBuilder().withInterestTypeAsDecliningBalance().withTranches(true) .withInterestCalculationPeriodTypeAsRepaymentPeriod(true).build(null)); - LOG.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}", + log.info("----------------------------------LOAN PRODUCT CREATED WITH ID------------------------------------------- {}", loanProductID); - this.loanID = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, createTranches); - LOG.info("-----------------------------------LOAN CREATED WITH LOANID------------------------------------------------- {}", loanID); + this.loanId = applyForLoanApplicationWithTranches(clientID, loanProductID, proposedAmount, createTranches); + log.info("-----------------------------------LOAN CREATED WITH LOANID------------------------------------------------- {}", + this.loanId); - HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId); LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); - LOG.info("-----------------------------------APPROVE LOAN-----------------------------------------------------------"); + log.info("-----------------------------------APPROVE LOAN-----------------------------------------------------------"); loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, expectedDisbursementDate, approvalAmount, - loanID, createTranches); + this.loanId, createTranches); LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); - LOG.info("-----------------------------------MULTI DISBURSAL LOAN APPROVED SUCCESSFULLY---------------------------------------"); + log.info("-----------------------------------MULTI DISBURSAL LOAN APPROVED SUCCESSFULLY---------------------------------------"); ArrayList disbursementDetails = (ArrayList) this.loanTransactionHelper.getLoanDetail(this.requestSpec, - this.responseSpec, this.loanID, "disbursementDetails"); + this.responseSpec, this.loanId, "disbursementDetails"); this.disbursementId = (Integer) disbursementDetails.get(0).get("id"); this.editLoanDisbursementDetails(); } @@ -386,7 +356,7 @@ public class LoanDisbursementDetailsIntegrationTest { private void addNewDisbursementDetails() throws ParseException { List addTranches = new ArrayList<>(); ArrayList disbursementDetails = (ArrayList) this.loanTransactionHelper.getLoanDetail(this.requestSpec, - this.responseSpec, this.loanID, "disbursementDetails"); + this.responseSpec, this.loanId, "disbursementDetails"); ArrayList expectedDisbursementDate = (ArrayList) disbursementDetails.get(0).get("expectedDisbursementDate"); String date = formatExpectedDisbursementDate(expectedDisbursementDate.toString()); @@ -397,14 +367,14 @@ public class LoanDisbursementDetailsIntegrationTest { addTranches.add(this.loanTransactionHelper.createTrancheDetail(id, "04 March 2014", "500")); /* Add disbursement detail */ - this.loanTransactionHelper.addAndDeleteDisbursementDetail(this.loanID, this.approvalAmount, this.expectedDisbursementDate, + this.loanTransactionHelper.addAndDeleteDisbursementDetail(this.loanId, this.approvalAmount, this.expectedDisbursementDate, addTranches, ""); } private void deleteDisbursmentDetails() throws ParseException { List deleteTranches = new ArrayList<>(); ArrayList disbursementDetails = (ArrayList) this.loanTransactionHelper.getLoanDetail(this.requestSpec, - this.responseSpec, this.loanID, "disbursementDetails"); + this.responseSpec, this.loanId, "disbursementDetails"); /* Delete the last tranche */ for (int i = 0; i < disbursementDetails.size() - 1; i++) { ArrayList expectedDisbursementDate = (ArrayList) disbursementDetails.get(i).get("expectedDisbursementDate"); @@ -414,7 +384,7 @@ public class LoanDisbursementDetailsIntegrationTest { } /* Add disbursement detail */ - this.loanTransactionHelper.addAndDeleteDisbursementDetail(this.loanID, this.approvalAmount, this.expectedDisbursementDate, + this.loanTransactionHelper.addAndDeleteDisbursementDetail(this.loanId, this.approvalAmount, this.expectedDisbursementDate, deleteTranches, ""); } @@ -422,11 +392,11 @@ public class LoanDisbursementDetailsIntegrationTest { String updatedExpectedDisbursementDate = "01 March 2014"; String updatedPrincipal = "900"; /* Update */ - this.loanTransactionHelper.editDisbursementDetail(this.loanID, this.disbursementId, this.approvalAmount, + this.loanTransactionHelper.editDisbursementDetail(this.loanId, this.disbursementId, this.approvalAmount, this.expectedDisbursementDate, updatedExpectedDisbursementDate, updatedPrincipal, ""); /* Validate Edit */ ArrayList disbursementDetails = (ArrayList) this.loanTransactionHelper.getLoanDetail(this.requestSpec, - this.responseSpec, this.loanID, "disbursementDetails"); + this.responseSpec, this.loanId, "disbursementDetails"); assertEquals(Float.parseFloat(updatedPrincipal), disbursementDetails.get(0).get("principal")); ArrayList expectedDisbursementDate = (ArrayList) disbursementDetails.get(0).get("expectedDisbursementDate"); String date = formatExpectedDisbursementDate(expectedDisbursementDate.toString()); @@ -435,7 +405,6 @@ public class LoanDisbursementDetailsIntegrationTest { } private String formatExpectedDisbursementDate(String expectedDisbursementDate) throws ParseException { - SimpleDateFormat source = new SimpleDateFormat("[yyyy, MM, dd]"); SimpleDateFormat target = new SimpleDateFormat("dd MMMM yyyy", Locale.US); String date = target.format(source.parse(expectedDisbursementDate)); @@ -443,35 +412,56 @@ public class LoanDisbursementDetailsIntegrationTest { return date; } - private Integer applyForLoanApplicationWithTranches(final Integer clientID, final Integer loanProductID, String principal, + private Integer applyForLoanApplicationWithTranches(final Integer clientId, final Integer loanProductId, String principal, List tranches) { - LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + log.info("----------------APPLYING FOR LOAN APPLICATION"); List collaterals = new ArrayList<>(); final Integer collateralId = CollateralManagementHelper.createCollateralProduct(this.requestSpec, this.responseSpec); Assertions.assertNotNull(collateralId); final Integer clientCollateralId = CollateralManagementHelper.createClientCollateral(this.requestSpec, this.responseSpec, - clientID.toString(), collateralId); + clientId.toString(), collateralId); Assertions.assertNotNull(clientCollateralId); addCollaterals(collaterals, clientCollateralId, BigDecimal.valueOf(1)); - final String loanApplicationJSON = new LoanApplicationTestBuilder() - // - .withPrincipal(principal) - // - .withLoanTermFrequency("5") - // - .withLoanTermFrequencyAsMonths() - // - .withNumberOfRepayments("5").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() // + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal(principal) // + .withLoanTermFrequency("5") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("5") // + .withRepaymentEveryAfter("1") // + .withRepaymentFrequencyTypeAsMonths() // .withInterestRatePerPeriod("2") // .withExpectedDisbursementDate("01 March 2014") // .withTranches(tranches) // .withInterestTypeAsDecliningBalance() // .withSubmittedOnDate("01 March 2014") // - .withCollaterals(collaterals).build(clientID.toString(), loanProductID.toString(), null); + .withCollaterals(collaterals).build(clientId.toString(), loanProductId.toString(), null); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); } + private Integer applyForMultiTrancheLoanApplication(final String clientId, final String loanProductId, String principal, + String operationDate) { + log.info("----------------APPLYING FOR MULTI TRANCHE LOAN APPLICATION"); + List emptyData = new ArrayList<>(); + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal(principal) // + .withLoanTermFrequency("3") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("3") // + .withRepaymentEveryAfter("1") // + .withRepaymentFrequencyTypeAsMonths() // + .withInterestRatePerPeriod("0") // + .withExpectedDisbursementDate(operationDate) // + .withTranches(emptyData) // + .withInterestTypeAsDecliningBalance() // + .withSubmittedOnDate(operationDate) // + .withCollaterals(emptyData) // + .build(clientId, loanProductId, null); + + log.info("Loan account {}", loanApplicationJSON); + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } + private void addCollaterals(List collaterals, Integer collateralId, BigDecimal quantity) { collaterals.add(collaterals(collateralId, quantity)); } @@ -483,4 +473,24 @@ public class LoanDisbursementDetailsIntegrationTest { return collateral; } + public void evaluateEqualInstallmentsForRepaymentSchedule(GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule, Double limit) { + Double totalOutstandingForPeriod = 0.0; + Double totalInstallmentAmountForPeriod = 0.0; + if (getLoanRepaymentSchedule != null) { + log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size()); + for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) { + if (period.getPeriod() != null) { + log.info("Period number {} for due date {} and outstanding {} {}", period.getPeriod(), period.getDueDate(), + period.getTotalOutstandingForPeriod(), period.getTotalInstallmentAmountForPeriod()); + if (period.getPeriod() == 1) { + totalOutstandingForPeriod = period.getTotalOutstandingForPeriod(); + totalInstallmentAmountForPeriod = period.getTotalInstallmentAmountForPeriod(); + } else { + assertTrue(Math.abs(period.getTotalOutstandingForPeriod() - totalOutstandingForPeriod) <= limit); + assertTrue(Math.abs(period.getTotalInstallmentAmountForPeriod() - totalInstallmentAmountForPeriod) <= limit); + } + } + } + } + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java index 1e4efa84b..9afe6f55f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java @@ -245,7 +245,7 @@ public class LoanRepaymentRescheduleAtDisbursementTest { .withInterestCalculationPeriodTypeAsDays() // .withExpectedDisbursementDate(disbursementDate) // .withSubmittedOnDate(disbursementDate) // - .withwithRepaymentStrategy(repaymentStrategy) // + .withRepaymentStrategy(repaymentStrategy) // .withCollaterals(collaterals).withCharges(charges)// .build(clientID.toString(), loanProductID.toString(), null); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleWithAdvancePaymentTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleWithAdvancePaymentTest.java index 2e8616f06..0f78248d8 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleWithAdvancePaymentTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleWithAdvancePaymentTest.java @@ -204,7 +204,7 @@ public class LoanRescheduleWithAdvancePaymentTest { .withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays() .withInterestRatePerPeriod("12").withInterestTypeAsDecliningBalance().withSubmittedOnDate(submittedDate) .withExpectedDisbursementDate(submittedDate).withFirstRepaymentDate(firstRepaymentDate) - .withwithRepaymentStrategy(LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY) + .withRepaymentStrategy(LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY) .withinterestChargedFromDate(submittedDate).build(this.clientId.toString(), this.loanProductId.toString(), null); this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON); @@ -318,7 +318,7 @@ public class LoanRescheduleWithAdvancePaymentTest { .withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays() .withInterestRatePerPeriod("12").withInterestTypeAsDecliningBalance().withSubmittedOnDate(submittedDate) .withExpectedDisbursementDate(submittedDate).withFirstRepaymentDate(firstRepaymentDate) - .withwithRepaymentStrategy(LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY) + .withRepaymentStrategy(LoanApplicationTestBuilder.INTEREST_PRINCIPAL_PENALTIES_FEES_ORDER_STRATEGY) .withinterestChargedFromDate(submittedDate).build(this.clientId.toString(), this.loanProductId.toString(), null); this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java index b2db9dd80..8c1ebbd6f 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java @@ -409,7 +409,7 @@ public class LoanReschedulingWithinCenterTest { .withInterestCalculationPeriodTypeAsDays() // .withExpectedDisbursementDate(disbursementDate) // .withSubmittedOnDate(disbursementDate) // - .withwithRepaymentStrategy(repaymentStrategy) // + .withRepaymentStrategy(repaymentStrategy) // .withCollaterals(collaterals).withCharges(charges)// .build(clientID.toString(), groupId.toString(), loanProductID.toString(), null); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAuditingIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAuditingIntegrationTest.java index 8475f84ba..4817046e2 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAuditingIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAuditingIntegrationTest.java @@ -187,7 +187,7 @@ public class LoanTransactionAuditingIntegrationTest { .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // .withExpectedDisbursementDate(submittedOnDate) // .withSubmittedOnDate(submittedOnDate) // - .withwithRepaymentStrategy(repaymentStrategy) // + .withRepaymentStrategy(repaymentStrategy) // .withCharges(charges).build(clientID.toString(), loanProductID.toString(), savingsId); return this.loanTransactionHelper.getLoanId(loanApplicationJSON); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java index a480de000..94ae8bf60 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -335,7 +335,7 @@ public class LoanApplicationTestBuilder { return this; } - public LoanApplicationTestBuilder withwithRepaymentStrategy(final String transactionProcessingStrategy) { + public LoanApplicationTestBuilder withRepaymentStrategy(final String transactionProcessingStrategy) { this.transactionProcessingID = transactionProcessingStrategy; return this; } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java index 4d9ff06bd..936dee5f4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java @@ -98,6 +98,7 @@ public class LoanProductTestBuilder { private Boolean allowApprovedDisbursedAmountsOverApplied = false; private String overAppliedCalculationType = null; private Integer overAppliedNumber = null; + private Boolean isEqualAmortization = false; private Boolean isInterestRecalculationEnabled = false; private String daysInYearType = "1"; @@ -168,6 +169,7 @@ public class LoanProductTestBuilder { map.put("accountingRule", this.accountingRule); map.put("minPrincipal", this.minPrincipal); map.put("maxPrincipal", this.maxPrincipal); + map.put("isEqualAmortization", this.isEqualAmortization); map.put("overdueDaysForNPA", this.overdueDaysForNPA); if (this.minimumDaysBetweenDisbursalAndFirstRepayment != null) { map.put("minimumDaysBetweenDisbursalAndFirstRepayment", this.minimumDaysBetweenDisbursalAndFirstRepayment); @@ -382,6 +384,11 @@ public class LoanProductTestBuilder { return this; } + public LoanProductTestBuilder withEqualAmortization(boolean isEqualAmortization) { + this.isEqualAmortization = isEqualAmortization; + return this; + } + public LoanProductTestBuilder withMultiDisburse() { this.multiDisburseLoan = true; return this; diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index ae7379972..890d2d6f2 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -42,6 +42,8 @@ import javax.ws.rs.core.MediaType; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.GetDelinquencyTagHistoryResponse; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; +import org.apache.fineract.client.models.GetLoansLoanIdRepaymentSchedule; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.util.JSON; @@ -1062,4 +1064,16 @@ public class LoanTransactionHelper { "---------------------------------GET A LOAN TRANSACTION ENTITY AUDIT FIELDS---------------------------------------------"); return Utils.performServerGet(requestSpec, responseSpec, GET_LOAN_TRANSACTION_URL, jsonReturn); } + + public void printRepaymentSchedule(GetLoansLoanIdResponse getLoansLoanIdResponse) { + GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule = getLoansLoanIdResponse.getRepaymentSchedule(); + if (getLoanRepaymentSchedule != null) { + log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size()); + for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) { + log.info("Period number {} for due date {} and outstanding {}", period.getPeriod(), period.getDueDate(), + period.getTotalOutstandingForPeriod()); + } + } + } + }