From 87217fb29e79496978e4e83f3b4b0b32d8c4626f Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Sat, 16 Aug 2014 14:28:53 +0530 Subject: [PATCH] MIFOSX-1418 : added regeneration of accrual entries --- .../AccountingScenarioIntegrationTest.java | 2 +- .../common/loans/LoanProductTestBuilder.java | 13 ++- .../portfolio/loanaccount/domain/Loan.java | 39 ++++++++- .../domain/LoanAccountDomainService.java | 9 +++ .../domain/LoanAccountDomainServiceJpa.java | 79 ++++++++++++++++++- .../domain/AbstractLoanScheduleGenerator.java | 37 ++++++++- .../LoanAccrualWritePlatformService.java | 5 ++ .../LoanAccrualWritePlatformServiceImpl.java | 19 +++-- .../service/LoanReadPlatformServiceImpl.java | 6 +- ...WritePlatformServiceJpaRepositoryImpl.java | 21 +++-- 10 files changed, 207 insertions(+), 23 deletions(-) diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java index 3a0392bc7..b8054ef47 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/AccountingScenarioIntegrationTest.java @@ -810,7 +810,7 @@ public class AccountingScenarioIntegrationTest { .withRepaymentAfterEvery(this.LP_REPAYMENT_PERIOD).withNumberOfRepayments(this.LP_REPAYMENTS).withRepaymentTypeAsMonth() .withinterestRatePerPeriod(this.LP_INTEREST_RATE).withInterestRateFrequencyTypeAsMonths() .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts) - .build(null); + .withDaysInMonth("30").withDaysInYear("365").build(null); return this.loanTransactionHelper.getLoanProductId(loanProductJSON); } diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java index 01d64cabe..8eee81efb 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java @@ -31,7 +31,7 @@ public class LoanProductTestBuilder { public static final String MIFOS_STANDARD_STRATEGY = "1"; // private static final String HEAVENS_FAMILY_STRATEGY ="2"; // private static final String CREO_CORE_STRATEGY ="3"; - public static final String RBI_INDIA_STRATEGY ="4"; + public static final String RBI_INDIA_STRATEGY = "4"; private static final String NONE = "1"; private static final String CASH_BASED = "2"; private static final String ACCRUAL_PERIODIC = "3"; @@ -63,7 +63,7 @@ public class LoanProductTestBuilder { private Boolean multiDisburseLoan = false; private String outstandingLoanBalance = "35000"; private String maxTrancheCount = "35000"; - + private Boolean isInterestRecalculationEnabled = false; private String daysInYearType = "1"; private String daysInMonthType = "1"; @@ -328,4 +328,13 @@ public class LoanProductTestBuilder { return this; } + public LoanProductTestBuilder withDaysInMonth(final String daysInMonthType) { + this.daysInMonthType = daysInMonthType; + return this; + } + + public LoanProductTestBuilder withDaysInYear(final String daysInYearType) { + this.daysInYearType = daysInYearType; + return this; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 1dec47747..adfed930b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -984,9 +984,36 @@ public class Loan extends AbstractPersistable { updateLoanScheduleDependentDerivedFields(); updateLoanSummaryDerivedFields(); + resetAccruals(); } + /** + * method to reverse all the accrual entries + */ + private void resetAccruals() { + if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return; } + List transactions = this.getLoanTransactions(); + for (final LoanTransaction transaction : transactions) { + if (transaction.isAccrual()) { + transaction.reverse(); + } + } + } + + public boolean isAccrualsPresent() { + boolean isAccrualsPresent = false; + if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return isAccrualsPresent; } + List transactions = this.getLoanTransactions(); + for (final LoanTransaction transaction : transactions) { + if (transaction.isAccrual() && transaction.isNotReversed()) { + isAccrualsPresent = true; + break; + } + } + return isAccrualsPresent; + } + private void updateLoanScheduleDependentDerivedFields() { this.expectedMaturityDate = determineExpectedMaturityDate().toDate(); this.actualMaturityDate = determineExpectedMaturityDate().toDate(); @@ -3218,7 +3245,7 @@ public class Loan extends AbstractPersistable { return matchesCurrentLoanOfficer; } - private LocalDate getInterestChargedFromDate() { + public LocalDate getInterestChargedFromDate() { LocalDate interestChargedFrom = null; if (this.interestChargedFromDate != null) { interestChargedFrom = new LocalDate(this.interestChargedFromDate); @@ -3415,7 +3442,7 @@ public class Loan extends AbstractPersistable { return this.loanProduct.isPeriodicAccrualAccountingEnabled(); } - private Long productId() { + public Long productId() { return this.loanProduct.getId(); } @@ -4170,4 +4197,12 @@ public class Loan extends AbstractPersistable { public List fetchRepaymentScheduleInstallments() { return this.repaymentScheduleInstallments; } + + public LocalDate getAccruedTill() { + LocalDate accruedTill = null; + if (this.accruedTill != null) { + accruedTill = new LocalDate(this.accruedTill); + } + return accruedTill; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java index 591ef3550..c98fbb83f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainService.java @@ -30,4 +30,13 @@ public interface LoanAccountDomainService { PaymentDetail paymentDetail, String noteText, String txnExternalId); LocalDate getCalculatedRepaymentsStartingFromDate(LocalDate actualDisbursementDate, Loan loan, CalendarInstance calendarInstance); + + /** + * This method is to recalculate and accrue the income till the last accrued + * date. this method is used when the schedule changes due to interest + * recalculation + * + * @param loan + */ + void recalculateAccruals(Loan loan); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 326aaa1b9..ad8d7793e 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -8,8 +8,10 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; @@ -22,6 +24,7 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.holiday.domain.HolidayRepository; import org.mifosplatform.organisation.holiday.domain.HolidayStatusType; +import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; @@ -37,9 +40,14 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; +import org.mifosplatform.portfolio.common.domain.DaysInMonthType; +import org.mifosplatform.portfolio.common.domain.DaysInYearType; +import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; +import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; +import org.mifosplatform.portfolio.loanaccount.service.LoanAccrualWritePlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.note.domain.Note; @@ -68,6 +76,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final CalendarInstanceRepository calendarInstanceRepository; private final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository; + private final LoanAccrualWritePlatformService accrualWritePlatformService; @Autowired public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, @@ -79,7 +88,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final AccountTransferRepository accountTransferRepository, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, final CalendarInstanceRepository calendarInstanceRepository, - final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository) { + final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository, + final LoanAccrualWritePlatformService accrualWritePlatformService) { this.loanAccountAssembler = loanAccountAssembler; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -94,6 +104,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { this.applicationCurrencyRepository = applicationCurrencyRepository; this.calendarInstanceRepository = calendarInstanceRepository; this.repaymentScheduleInstallmentRepository = repaymentScheduleInstallmentRepository; + this.accrualWritePlatformService = accrualWritePlatformService; } @Transactional @@ -187,6 +198,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); + recalculateAccruals(loan); builderResult.withEntityId(newRepaymentTransaction.getId()) // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // @@ -210,7 +222,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { "Validation errors exist.", dataValidationErrors); } } } - + private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) { try { List installments = loan.fetchRepaymentScheduleInstallments(); @@ -270,6 +282,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); + recalculateAccruals(loan); return newPaymentTransaction; } @@ -364,7 +377,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); - return disbursementTransaction; } @@ -402,6 +414,67 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { return calculatedRepaymentsStartingFromDate; } + /* (non-Javadoc) + * @see org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService#recalculateAccruals(org.mifosplatform.portfolio.loanaccount.domain.Loan) + */ + @Override + public void recalculateAccruals(Loan loan) { + LocalDate accruedTill = loan.getAccruedTill(); + if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || accruedTill == null || loan.isAccrualsPresent()) { return; } + Collection loanScheduleAccrualDatas = new ArrayList<>(); + List installments = loan.fetchRepaymentScheduleInstallments(); + Long loanId = loan.getId(); + DaysInMonthType daysInMonth = loan.repaymentScheduleDetail().fetchDaysInMonthType(); + DaysInYearType daysInYear = loan.repaymentScheduleDetail().fetchDaysInYearType(); + Long officeId = loan.getOfficeId(); + LocalDate accrualStartDate = null; + PeriodFrequencyType repaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType(); + Integer repayEvery = loan.repaymentScheduleDetail().getRepayEvery(); + LocalDate interestCalculatedFrom = loan.getInterestChargedFromDate(); + Long loanProductId = loan.productId(); + MonetaryCurrency currency = loan.getCurrency(); + ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + CurrencyData currencyData = applicationCurrency.toData(); + Set loanCharges = loan.charges(); + BigDecimal accruedInterestIncome = null; + BigDecimal accruedFeeIncome = null; + BigDecimal accruedPenaltyIncome = null; + + for (LoanRepaymentScheduleInstallment installment : installments) { + if (!accruedTill.isBefore(installment.getDueDate()) + || (accruedTill.isAfter(installment.getFromDate()) && !accruedTill.isAfter(installment.getDueDate()))) { + BigDecimal dueDateFeeIncome = BigDecimal.ZERO; + BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO; + LocalDate chargesTillDate = installment.getDueDate(); + if (!accruedTill.isAfter(installment.getDueDate())) { + chargesTillDate = accruedTill; + } + + for (final LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)) { + if (loanCharge.isFeeCharge()) { + dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount()); + } else if (loanCharge.isPenaltyCharge()) { + dueDatePenaltyIncome = dueDatePenaltyIncome.add(loanCharge.amount()); + } + } + } + LoanScheduleAccrualData accrualData = new LoanScheduleAccrualData(loanId, officeId, accrualStartDate, + daysInMonth.getValue(), daysInYear.getValue(), repaymentFrequency, repayEvery, installment.getDueDate(), + installment.getFromDate(), installment.getId(), loanProductId, + installment.getInterestCharged(currency).getAmount(), installment.getFeeChargesCharged(currency).getAmount(), + installment.getPenaltyChargesCharged(currency).getAmount(), accruedInterestIncome, accruedFeeIncome, + accruedPenaltyIncome, currencyData, dueDateFeeIncome, dueDatePenaltyIncome, interestCalculatedFrom); + loanScheduleAccrualDatas.add(accrualData); + + } + } + + if (!loanScheduleAccrualDatas.isEmpty()) { + this.accrualWritePlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualDatas); + } + } + private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); if (transferTransaction != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index d3c496e6c..d768a95b8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -146,6 +146,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); Money balanceForcalculation = outstandingBalance; + // reduce principal is the early payment, will processed as per the + // reschedule strategy if (reducePrincipal.isGreaterThanZero()) { switch (loanApplicationTerms.getRescheduleStrategyMethod()) { case REDUCE_EMI_AMOUNT: @@ -211,10 +213,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment); } + // this block is to identify interest based on late/early payment if (diffAmt != null && !diffAmt.isEmpty()) { BigDecimal interestDueToLatePayment = BigDecimal.ZERO; BigDecimal interestReducedDueToEarlyPayment = BigDecimal.ZERO; for (RecalculationDetail detail : diffAmt) { + // will increase the principal portion and reduces interest + // as per the number of days. and also identifies reduce + // principal for reschedule strategy if (!detail.isLatePayment() && detail.getStartDate().isAfter(periodStartDate) && !detail.getStartDate().isAfter(scheduledDueDate)) { reducePrincipal = reducePrincipal.plus(detail.getAmount()); @@ -234,7 +240,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.add(loanApplicationTerms.interestRateFor( this.paymentPeriodsInOneYearCalculator, mc, detail.getAmount(), detail.getStartDate(), scheduledDueDate)); - } else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate) + } + // calculates the interest for late payment and increase + // the interest for the installment + else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate) && periodStartDate.isBefore(LocalDate.now())) { LocalDate fromDate = periodStartDate; LocalDate toDate = scheduledDueDate; @@ -312,6 +321,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } + // this block is to add extra re-payment schedules with interest portion + // if the last payment is missed if (diffAmt != null && !diffAmt.isEmpty() && !periodStartDate.isAfter(LocalDate.now())) { Map processDetails = new TreeMap<>(); for (RecalculationDetail detail : diffAmt) { @@ -518,6 +529,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return cumulative; } + /** + * Method calls schedule regeneration by passing transactions one after + * another(this is done mainly to handle the scenario where interest or fee + * of over due installment should be collected before collecting principal ) + */ @Override public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final ApplicationCurrency applicationCurrency, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, @@ -570,6 +586,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return recalculateFrom; } + /** + * Method calls regenerate schedule for a particular set of transactions + * till all the installments are processed to identify early or late + * payments and will be used in regeneration of schedule + */ private LoanScheduleModel recalculateInstallment(final MathContext mc, final ApplicationCurrency applicationCurrency, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, @@ -593,6 +614,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return loanScheduleModel; } + /** + * Method identifies late or early payments for schedule recalculation. + */ private RecalculatedSchedule recalculateInterest(final MathContext mc, final ApplicationCurrency applicationCurrency, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final List transactions, @@ -638,6 +662,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Map earlyPaymentMap = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule( transactionsForInstallment, currency, processinstallmets, installment, recalculationDates); + // this block is to create early payment entries for schedule + // generation for (Map.Entry entry : earlyPaymentMap.entrySet()) { LocalDate startDate = entry.getKey(); if (unpaidPricipal.isGreaterThan(entry.getValue())) { @@ -655,6 +681,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money totalOutstanding = installment.getTotalOutstanding(currency); boolean reduceStartDate = false; + // this block is to identify late payment based on the rest + // calculation date while (totalOutstanding.isGreaterThanZero() && startDate.isBefore(LocalDate.now())) { LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays, workingDays); @@ -725,6 +753,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return installments; } + /** + * Method to identify which transaction did the payment for current + * installment + */ private void applyRest(List loanTransactions, LocalDate from, LocalDate to, LoanRepaymentScheduleInstallment installment, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency, @@ -760,6 +792,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return nextScheduleDate; } + /** + * Method returns the amount payable to close the loan account as of today. + */ @Override public Money fetchPrepaymentAmount(final List installments, MonetaryCurrency currency, final LoanApplicationTerms applicationTerms, final MathContext mc) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java index 80d76e6f9..c79b34878 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java @@ -5,8 +5,11 @@ */ package org.mifosplatform.portfolio.loanaccount.service; +import java.util.Collection; + import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException; +import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData; public interface LoanAccrualWritePlatformService { @@ -17,4 +20,6 @@ public interface LoanAccrualWritePlatformService { String addPeriodicAccruals(LocalDate tilldate); + String addPeriodicAccruals(LocalDate tilldate, Collection loanScheduleAccrualDatas); + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java index 48c17f9b6..29eeb25cb 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java @@ -95,7 +95,8 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat return addPeriodicAccruals(tilldate, loanScheduleAccrualDatas); } - private String addPeriodicAccruals(final LocalDate tilldate, Collection loanScheduleAccrualDatas) { + @Override + public String addPeriodicAccruals(final LocalDate tilldate, Collection loanScheduleAccrualDatas) { StringBuilder sb = new StringBuilder(); Set loansIds = new HashSet<>(); LocalDate accruredTill = null; @@ -129,16 +130,18 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat private void addAccrualTillSpecificDate(final LocalDate tilldate, final LoanScheduleAccrualData accrualData, final LocalDate accruredTill) throws Exception { - LocalDate interestStartDate = accrualData.getFromDateAsLocaldate(); + LocalDate interestStartDate = accrualData.getFromDateAsLocaldate(); + boolean isInterestStartDateEqulsInstallmentStartdate = true; if (accrualData.getInterestCalculatedFrom() != null && accrualData.getFromDateAsLocaldate().isBefore(accrualData.getInterestCalculatedFrom())) { + isInterestStartDateEqulsInstallmentStartdate = false; if (accrualData.getInterestCalculatedFrom().isBefore(accrualData.getDueDateAsLocaldate())) { interestStartDate = accrualData.getInterestCalculatedFrom(); } else { interestStartDate = accrualData.getDueDateAsLocaldate(); } } - + int totalNumberOfDays = Days.daysBetween(interestStartDate, accrualData.getDueDateAsLocaldate()).getDays(); LocalDate startDate = accruredTill; if (startDate == null) { @@ -156,10 +159,16 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat int daysInSchedule = Days.daysBetween(interestStartDate, tilldate).getDays(); switch (accrualData.getRepaymentFrequency()) { case MONTHS: - totalNumberOfDays = calculateTotalNumberOfDaysForMonth(totalNumberOfDays, accrualData); + int numberOfDaysInMonth = calculateTotalNumberOfDaysForMonth(totalNumberOfDays, accrualData); + if (!(!isInterestStartDateEqulsInstallmentStartdate && totalNumberOfDays < numberOfDaysInMonth)) { + totalNumberOfDays = numberOfDaysInMonth; + } break; case YEARS: - totalNumberOfDays = calculateTotalNumberOfDaysForYear(totalNumberOfDays, accrualData); + int numberOfDaysinYear = calculateTotalNumberOfDaysForYear(totalNumberOfDays, accrualData); + if (!(!isInterestStartDateEqulsInstallmentStartdate && totalNumberOfDays < numberOfDaysinYear)) { + totalNumberOfDays = numberOfDaysinYear; + } break; default: break; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 794cbc21d..22edf00cf 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -1424,6 +1424,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { .append("loan.id as loanId ,if(loan.client_id is null,mg.office_id,mc.office_id) as officeId,") .append("loan.accrued_till as accruedTill, loan.repayment_period_frequency_enum as frequencyEnum, ") .append("loan.interest_calculated_from_date as interestCalculatedFrom, ") + .append("loan.days_in_month_enum as daysInMonth, ") + .append("loan.days_in_year_enum as daysInYear, ") .append("loan.repay_every as repayEvery,") .append("(select sum(lc.amount) as dueamount from m_loan_charge lc where lc.loan_id = loan.id and lc.is_active = 1 and lc.waived = 0 and lc.is_penalty = 0 and lc.due_for_collection_as_of_date > ls.fromdate and lc.due_for_collection_as_of_date<= if(:tilldate < ls.duedate, :tilldate , ls.duedate)) as feedueIn,") .append("(select sum(lc.amount) as dueamount from m_loan_charge lc where lc.loan_id = loan.id and lc.is_active = 1 and lc.waived = 0 and lc.is_penalty = 1 and lc.due_for_collection_as_of_date > ls.fromdate and lc.due_for_collection_as_of_date<= if(:tilldate < ls.duedate, :tilldate , ls.duedate)) as penalitydueIn,") @@ -1448,8 +1450,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Long officeId = rs.getLong("officeId"); final LocalDate accruedTill = JdbcSupport.getLocalDate(rs, "accruedTill"); final LocalDate interestCalculatedFrom = JdbcSupport.getLocalDate(rs, "interestCalculatedFrom"); - final Integer numberOfDaysInMonth = 30; - final Integer numberOfDaysInYear = 360; + final Integer numberOfDaysInMonth = JdbcSupport.getInteger(rs, "daysInMonth");; + final Integer numberOfDaysInYear = JdbcSupport.getInteger(rs, "daysInYear");; final Integer frequencyEnum = JdbcSupport.getInteger(rs, "frequencyEnum"); final Integer repayEvery = JdbcSupport.getInteger(rs, "repayEvery"); final PeriodFrequencyType frequency = PeriodFrequencyType.fromInt(frequencyEnum); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 26c56a823..a929b9f52 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -315,6 +315,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + } final Set loanCharges = loan.charges(); @@ -340,7 +341,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } updateRecurringCalendarDatesForInterestRecalculation(loan); - + this.loanAccountDomainService.recalculateAccruals(loan); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(loan.getId()) // @@ -526,7 +527,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } updateRecurringCalendarDatesForInterestRecalculation(loan); + this.loanAccountDomainService.recalculateAccruals(loan); } + return changes; } @@ -766,6 +769,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + this.loanAccountDomainService.recalculateAccruals(loan); + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(transactionId) // @@ -857,7 +862,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - + this.loanAccountDomainService.recalculateAccruals(loan); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(waiveInterestTransaction.getId()) // @@ -925,7 +930,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - + this.loanAccountDomainService.recalculateAccruals(loan); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(writeoff.getId()) // @@ -998,7 +1003,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (possibleClosingTransaction != null) { postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); } - + this.loanAccountDomainService.recalculateAccruals(loan); CommandProcessingResult result = null; if (possibleClosingTransaction != null) { @@ -2043,6 +2048,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf saveLoanWithDataIntegrityViolationChecks(loan); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + this.loanAccountDomainService.recalculateAccruals(loan); return new CommandProcessingResultBuilder() // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // @@ -2091,7 +2097,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); - + this.loanAccountDomainService.recalculateAccruals(loan); return new CommandProcessingResultBuilder() // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) // @@ -2205,8 +2211,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); workingDays = this.workingDaysRepository.findOne(); } - ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance, + ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(loanScheduleFactory, + applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance, existingTransactionIds, existingReversedTransactionIds); saveLoanWithDataIntegrityViolationChecks(loan); @@ -2218,6 +2224,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + this.loanAccountDomainService.recalculateAccruals(loan); } private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {