From ef3d19c387c9c25a30bdea143779ef15d6c60f27 Mon Sep 17 00:00:00 2001 From: Emmanuel Nnaa Date: Thu, 6 Nov 2014 14:46:28 +0100 Subject: [PATCH] commit for MIFOSX-1638 (Waiving charges attached to zero balance instalments - Loan Rescheduling) --- .../portfolio/loanaccount/domain/Loan.java | 23 ++- .../domain/AbstractLoanScheduleGenerator.java | 6 + .../LoanRescheduleRequestDataValidator.java | 30 --- ...heduleRequestWritePlatformServiceImpl.java | 177 ++++++++++++++++-- 4 files changed, 191 insertions(+), 45 deletions(-) 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 ef00c3a4f..a738eb0c5 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 @@ -4726,4 +4726,25 @@ public class Loan extends AbstractPersistable { } return isEnabled; } -} \ No newline at end of file + + public String getAccountNumber() { + return this.accountNumber; + } + + public Client getClient() { + return this.client; + } + + public Collection getLoanCharges(LocalDate dueDate) { + Collection loanCharges = new ArrayList<>(); + + for (LoanCharge loanCharge : charges) { + + if ((loanCharge.getDueLocalDate() != null) && loanCharge.getDueLocalDate().equals(dueDate)) { + loanCharges.add(loanCharge); + } + } + + return loanCharges; + } +} 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 cbcd0c8ee..8b0a7b649 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 @@ -794,6 +794,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money principalDue = Money.of(currency, period.principalDue()); Money interestDue = Money.of(currency, period.interestDue()); + + if(principalDue.isZero() && interestDue.isZero()) { + period.updateFeeChargesDue(Money.zero(currency)); + period.updatePenaltyChargesDue(Money.zero(currency)); + } + Money feeChargesDue = Money.of(currency, period.feeChargesDue()); Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java index c1235f91a..58320adf3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/data/LoanRescheduleRequestDataValidator.java @@ -150,21 +150,6 @@ public class LoanRescheduleRequestDataValidator { dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName) .failWithCode("repayment.schedule.installment.partly.paid", "Repayment schedule installment is partly paid"); } - - if (installment != null) { - - if (installment.getPenaltyChargesOutstanding(loan.getCurrency()).isGreaterThanZero()) { - dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( - "repayment.schedule.installment.has.an.outstanding.penalty.charge", - "Repayment schedule installment has an outsatnding penalty charge."); - } - - if (installment.getFeeChargesOutstanding(loan.getCurrency()).isGreaterThanZero()) { - dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( - "repayment.schedule.installment.has.an.outstanding.fee.charge", - "Repayment schedule installment has an outsatnding fee charge."); - } - } } if (loanId != null) { @@ -243,21 +228,6 @@ public class LoanRescheduleRequestDataValidator { dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( "loan.repayment.schedule.installment." + "obligation.met", "Repayment schedule installment obligation met"); } - - if (installment != null) { - - if (installment.getPenaltyChargesOutstanding(loan.getCurrency()).isGreaterThanZero()) { - dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( - "repayment.schedule.installment.has.an.outstanding.penalty.charge", - "Repayment schedule installment has an outsatnding penalty charge."); - } - - if (installment.getFeeChargesOutstanding(loan.getCurrency()).isGreaterThanZero()) { - dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( - "repayment.schedule.installment.has.an.outstanding.fee.charge", - "Repayment schedule installment has an outsatnding fee charge."); - } - } } if (loanId != null) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 2fa8cfe53..10ff7725c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -8,6 +8,8 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.LinkedHashMap; @@ -17,6 +19,7 @@ import java.util.Map; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import org.mifosplatform.accounting.journalentry.service.JournalEntryWritePlatformService; import org.mifosplatform.infrastructure.codes.domain.CodeValue; import org.mifosplatform.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; @@ -26,23 +29,38 @@ import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; 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.holiday.domain.HolidayRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; +import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidByData; +import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; +import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; +import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.Loan; +import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService; +import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary; import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator; @@ -52,6 +70,8 @@ import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanResched import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException; +import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; +import org.mifosplatform.portfolio.loanaccount.service.LoanChargeReadPlatformService; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail; import org.mifosplatform.useradministration.domain.AppUser; @@ -74,10 +94,18 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche private final LoanRescheduleRequestRepository loanRescheduleRequestRepository; private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; private final ConfigurationDomainService configurationDomainService; - private final HolidayRepository holidayRepository; + private final HolidayRepositoryWrapper holidayRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository; private final LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository; private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; + private final CalendarInstanceRepository calendarInstanceRepository; + private final LoanAccountDomainService loanAccountDomainService; + private final LoanChargeReadPlatformService loanChargeReadPlatformService; + private final LoanScheduleGeneratorFactory loanScheduleFactory; + private final LoanTransactionRepository loanTransactionRepository; + private final JournalEntryWritePlatformService journalEntryWritePlatformService; + private final LoanRepository loanRepository; + private final LoanAssembler loanAssembler; /** * LoanRescheduleRequestWritePlatformServiceImpl constructor @@ -90,9 +118,17 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche LoanRescheduleRequestDataValidator loanRescheduleRequestDataValidator, LoanRescheduleRequestRepository loanRescheduleRequestRepository, ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, ConfigurationDomainService configurationDomainService, - HolidayRepository holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository, + HolidayRepositoryWrapper holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository, LoanRepaymentScheduleHistoryRepository loanRepaymentScheduleHistoryRepository, - final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService) { + final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, + final CalendarInstanceRepository calendarInstanceRepository, + final LoanAccountDomainService loanAccountDomainService, + final LoanChargeReadPlatformService loanChargeReadPlatformService, + final LoanScheduleGeneratorFactory loanScheduleFactory, + final LoanTransactionRepository loanTransactionRepository, + final JournalEntryWritePlatformService journalEntryWritePlatformService, + final LoanRepository loanRepository, + final LoanAssembler loanAssembler) { this.loanRepositoryWrapper = loanRepositoryWrapper; this.codeValueRepositoryWrapper = codeValueRepositoryWrapper; this.platformSecurityContext = platformSecurityContext; @@ -104,6 +140,14 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche this.workingDaysRepository = workingDaysRepository; this.loanRepaymentScheduleHistoryRepository = loanRepaymentScheduleHistoryRepository; this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService; + this.calendarInstanceRepository = calendarInstanceRepository; + this.loanAccountDomainService = loanAccountDomainService; + this.loanChargeReadPlatformService = loanChargeReadPlatformService; + this.loanScheduleFactory = loanScheduleFactory; + this.loanTransactionRepository = loanTransactionRepository; + this.journalEntryWritePlatformService = journalEntryWritePlatformService; + this.loanRepository = loanRepository; + this.loanAssembler = loanAssembler; } /** @@ -267,7 +311,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan - .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); + .getDisbursementDate().toDate()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency(); @@ -286,7 +330,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche final Collection periods = loanRescheduleModel.getPeriods(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); - + Collection waiveLoanCharges = new ArrayList<>(); + for (LoanRescheduleModelRepaymentPeriod period : periods) { if (period.isNew()) { @@ -302,6 +347,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche if (repaymentScheduleInstallment.getInstallmentNumber().equals(period.oldPeriodNumber())) { + LocalDate periodDueDate = repaymentScheduleInstallment.getDueDate(); + Money zeroAmount = Money.of(currency, new BigDecimal(0)); + repaymentScheduleInstallment.updateInstallmentNumber(period.periodNumber()); repaymentScheduleInstallment.updateFromDate(period.periodFromDate()); repaymentScheduleInstallment.updateDueDate(period.periodDueDate()); @@ -309,12 +357,13 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche repaymentScheduleInstallment.updateInterestCharged(period.interestDue()); if (Money.of(currency, period.principalDue()).isZero() && Money.of(currency, period.interestDue()).isZero() - && repaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).isZero() - && repaymentScheduleInstallment.getFeeChargesOutstanding(currency).isZero() && repaymentScheduleInstallment.isNotFullyPaidOff()) { - - repaymentScheduleInstallment.updateObligationMet(true); - repaymentScheduleInstallment.updateObligationMetOnDate(new LocalDate()); + + if(repaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).isGreaterThan(zeroAmount) || + repaymentScheduleInstallment.getFeeChargesOutstanding(currency).isGreaterThan(zeroAmount)) { + + waiveLoanCharges.addAll(loan.getLoanCharges(periodDueDate)); + } } break; @@ -329,6 +378,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche loan.updateRescheduledByUser(appUser); loan.updateRescheduledOnDate(new LocalDate()); + + // waive all loan charges of zero instalments + waiveLoanCharges(loan, waiveLoanCharges); // update the Loan summary loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true); @@ -342,6 +394,14 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche // update the status of the request loanRescheduleRequest.approve(appUser, approvedOnDate); + + // update the derived fields of each loan repayments schedule instalments + for (final LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) { + repaymentScheduleInstallment.updateDerivedFields(currency, new LocalDate()); + } + + // update the loan object + this.loanRepository.save(loan); } return new CommandProcessingResultBuilder().withCommandId(jsonCommand.commandId()).withEntityId(loanRescheduleRequestId) @@ -357,6 +417,97 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche } } + /** + * waive all charges in the collection + * + * @param loan Loan object + * @param loanCharges collection of LoanCharge objects + * @return void + **/ + private void waiveLoanCharges(Loan loan, Collection loanCharges) { + this.loanAssembler.setHelpers(loan); + + for (LoanCharge loanCharge : loanCharges) { + + if (loanCharge.isNotFullyPaid() && !loanCharge.isWaived()) { + Integer loanInstallmentNumber = null; + + if (loanCharge.isInstalmentFee()) { + LoanInstallmentCharge chargePerInstallment = loanCharge.getUnpaidInstallmentLoanCharge(); + + if (chargePerInstallment != null) { + loanInstallmentNumber = chargePerInstallment.getRepaymentInstallment().getInstallmentNumber(); + } + } + + final Map changes = new LinkedHashMap<>(3); + + final List existingTransactionIds = new ArrayList<>(); + final List existingReversedTransactionIds = new ArrayList<>(); + CalendarInstance restCalendarInstance = null; + ApplicationCurrency applicationCurrency = null; + LocalDate calculatedRepaymentsStartingFromDate = null; + List holidays = null; + boolean isHolidayEnabled = false; + WorkingDays workingDays = null; + LocalDate recalculateFrom = null; + LocalDate lastTransactionDate = null; + Long overdurPenaltyWaitPeriod = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + + final MonetaryCurrency currency = loan.getCurrency(); + applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), + CalendarEntityType.LOANS.getValue()); + calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + loan.getDisbursementDate(), loan, calendarInstance); + + isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); + workingDays = this.workingDaysRepository.findOne(); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + } + + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); + + Money accruedCharge = Money.zero(loan.getCurrency()); + if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { + Collection chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy( + loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber); + for (LoanChargePaidByData chargePaidByData : chargePaidByDatas) { + accruedCharge = accruedCharge.plus(chargePaidByData.getAmount()); + } + } + + final LoanTransaction loanTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes, + existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge); + + this.loanTransactionRepository.save(loanTransaction); + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + } + } + } + + private void postJournalEntries(Loan loan, List existingTransactionIds, List existingReversedTransactionIds) { + final MonetaryCurrency currency = loan.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + boolean isAccountTransfer = false; + final Map accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + } + + private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { + final List allowedLoanStatuses = Arrays.asList(LoanStatus.values()); + return new DefaultLoanLifecycleStateMachine(allowedLoanStatuses); + } + @Override @Transactional public CommandProcessingResult reject(JsonCommand jsonCommand) { @@ -411,7 +562,6 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche * data integrity violation exception * @return void **/ - @SuppressWarnings("unused") private void handleDataIntegrityViolation(final JsonCommand jsonCommand, final DataIntegrityViolationException dve) { logger.error(dve.getMessage(), dve); @@ -419,5 +569,4 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche throw new PlatformDataIntegrityException("error.msg.loan.reschedule.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } - }