diff --git a/mifosng-db/migrations/core_db/V192__interest_recalculate_job.sql b/mifosng-db/migrations/core_db/V192__interest_recalculate_job.sql new file mode 100755 index 000000000..8616fd27c --- /dev/null +++ b/mifosng-db/migrations/core_db/V192__interest_recalculate_job.sql @@ -0,0 +1,7 @@ +INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `scheduler_group`) VALUES ('Recalculate Interest For Loans', 'Recalculate Interest For Loans', '0 1 0 1/1 * ? *', now(), 4, 3); + +UPDATE `job` SET `scheduler_group`=3 WHERE `name`='Update Non Performing Assets'; + +UPDATE `job` SET `task_priority`=3 WHERE `name`='Add Accrual Transactions'; + +UPDATE `job` SET `task_priority`=2 WHERE `name`='Add Periodic Accrual Transactions'; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java index c7c65713c..9a67d2e4b 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobName.java @@ -22,7 +22,8 @@ public enum JobName { UPDATE_NPA("Update Non Performing Assets"), UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"), TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"), - ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"); + ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"), + RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans"); private final String name; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java index 9ee79e294..10bd67985 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarInstanceRepository.java @@ -32,8 +32,8 @@ public interface CalendarInstanceRepository extends JpaRepository findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java index 39eb09d34..ce53e9599 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -90,12 +90,13 @@ public class LoanTransactionsApiResource { transactionData = this.loanReadPlatformService.retrieveNewClosureDetails(); } else if (is(commandParam, "disburse")) { transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true); - } else if(is(commandParam, "disburseToSavings")){ + } else if (is(commandParam, "disburseToSavings")) { transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false); - } else if(is(commandParam, "recoverypayment")){ + } else if (is(commandParam, "recoverypayment")) { transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId); - } - else { + } else if (is(commandParam, "prepayLoan")) { + transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanId); + } else { throw new UnrecognizedQueryParamException("command", commandParam); } @@ -150,7 +151,7 @@ public class LoanTransactionsApiResource { } else if (is(commandParam, "undowriteoff")) { final CommandWrapper commandRequest = builder.undoWriteOffLoanTransaction(loanId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); - }else if(is(commandParam,"recoverypayment")){ + } else if (is(commandParam, "recoverypayment")) { final CommandWrapper commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build(); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargePaidDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargePaidDetail.java new file mode 100755 index 000000000..47f9b78aa --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanChargePaidDetail.java @@ -0,0 +1,33 @@ +package org.mifosplatform.portfolio.loanaccount.data; + +import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; + + +public class LoanChargePaidDetail { + + private final Money amount; + private final LoanRepaymentScheduleInstallment installment; + private final boolean isFeeCharge; + + public LoanChargePaidDetail( Money amount,LoanRepaymentScheduleInstallment installment,boolean isFeeCharge){ + this.amount = amount; + this.installment = installment; + this.isFeeCharge = isFeeCharge; + } + + + public Money getAmount() { + return this.amount; + } + + + public LoanRepaymentScheduleInstallment getInstallment() { + return this.installment; + } + + + public boolean isFeeCharge() { + return this.isFeeCharge; + } +} 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 7ff0dd953..baf154a64 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 @@ -60,6 +60,7 @@ import org.mifosplatform.organisation.staff.domain.Staff; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil; import org.mifosplatform.portfolio.accountdetails.domain.AccountType; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException; @@ -310,6 +311,9 @@ public class Loan extends AbstractPersistable { @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true) private LoanInterestRecalculationDetails loanInterestRecalculationDetails; + @Column(name = "is_npa", nullable = false) + private boolean isNpa; + public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy, @@ -496,8 +500,8 @@ public class Loan extends AbstractPersistable { // NOTE: must add new loan charge to set of loan charges before // reporcessing the repayment schedule. - if(this.charges == null) { - this.charges = new HashSet<>(); + if (this.charges == null) { + this.charges = new HashSet<>(); } this.charges.add(loanCharge); this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); @@ -1511,7 +1515,8 @@ public class Loan extends AbstractPersistable { validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); /** - * Copy interest recalculation settings if interest recalculation is enabled + * Copy interest recalculation settings if interest recalculation is + * enabled */ if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { this.loanInterestRecalculationDetails = this.loanProduct.copyInterestRecalculationSettings(); @@ -1722,7 +1727,8 @@ public class Loan extends AbstractPersistable { final ApplicationCurrency currency, final List existingTransactionIds, final List existingReversedTransactionIds, final Map actualChanges, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, - final boolean allowTransactionsOnNonWorkingDay, final boolean recalculateSchedule) { + final boolean allowTransactionsOnNonWorkingDay, final boolean recalculateSchedule, + final CalendarInstance calendarInstanceForInterestRecalculation) { final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); @@ -1765,6 +1771,11 @@ public class Loan extends AbstractPersistable { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } } + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() + && fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) { + regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + } updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); updateLoanSummaryDerivedFields(); handleDisbursementTransaction(actualDisbursementDate); @@ -2127,7 +2138,8 @@ public class Loan extends AbstractPersistable { } boolean isEmiAmountChanged = this.loanTermVariations.size() > 0; updateLoanToPreDisbursalState(); - if (isScheduleRegenerateRequired || isDisbueseAmtChanged || isEmiAmountChanged) { + if (isScheduleRegenerateRequired || isDisbueseAmtChanged || isEmiAmountChanged + || this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { // clear off actual disbusrement date so schedule regeneration // uses expected date. @@ -2186,7 +2198,9 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, - final List existingReversedTransactionIds) { + final List existingReversedTransactionIds, final List holidays, final WorkingDays workingDays, + final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, + final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER); @@ -2197,7 +2211,8 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction, - loanLifecycleStateMachine, null); + loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, + holidays, workingDays, calendarInstanceForInterestRecalculation); return changedTransactionDetail; } @@ -2205,7 +2220,9 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, final List existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List holidays, - final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, boolean isRecoveryRepayment) { + final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, final boolean isHolidayEnabled, + boolean isRecoveryRepayment, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, + final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { LoanEvent event = null; if (isRecoveryRepayment) { @@ -2224,7 +2241,8 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, - loanLifecycleStateMachine, null); + loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, + holidays, workingDays, calendarInstanceForInterestRecalculation); return changedTransactionDetail; } @@ -2289,7 +2307,10 @@ public class Loan extends AbstractPersistable { } private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction, - final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) { + final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction, + final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, + final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, + final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) { ChangedTransactionDetail changedTransactionDetail = null; @@ -2353,10 +2374,36 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - if (isTransactionChronologicallyLatest && adjustedTransaction == null) { + + final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment(loanTransaction + .getTransactionDate()); + boolean reprocess = true; + + if (adjustedTransaction == null && loanTransaction.getTransactionDate().isEqual(LocalDate.now()) && currentInstallment != null) { + reprocess = false; + } + + if (isTransactionChronologicallyLatest && adjustedTransaction == null + && (!reprocess || !this.repaymentScheduleDetail().isInterestRecalculationEnabled())) { loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments, charges()); - } else { + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) { + reprocess = true; + } else { + final LoanRepaymentScheduleInstallment nextInstallment = fetchRepaymentScheduleInstallment(currentInstallment + .getInstallmentNumber() + 1); + if (nextInstallment != null && nextInstallment.getTotalPaidInAdvance(getCurrency()).isGreaterThanZero()) { + reprocess = true; + } + } + } + } + if (reprocess) { + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); @@ -2384,6 +2431,17 @@ public class Loan extends AbstractPersistable { return changedTransactionDetail; } + private LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate dueDate) { + LoanRepaymentScheduleInstallment installment = null; + for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { + if (dueDate.equals(loanRepaymentScheduleInstallment.getDueDate())) { + installment = loanRepaymentScheduleInstallment; + break; + } + } + return installment; + } + private List retreiveListOfTransactionsPostDisbursement() { final List repaymentsOrWaivers = new ArrayList<>(); for (final LoanTransaction transaction : this.loanTransactions) { @@ -2556,7 +2614,9 @@ public class Loan extends AbstractPersistable { final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, final List existingTransactionIds, final List existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List holidays, final WorkingDays workingDays, - final boolean allowTransactionsOnNonWorkingDay) { + final boolean isHolidayEnabled, final boolean allowTransactionsOnNonWorkingDay, + final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, + final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), allowTransactionsOnHoliday, holidays); validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); @@ -2589,13 +2649,17 @@ public class Loan extends AbstractPersistable { if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) { changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, - transactionForAdjustment); + transactionForAdjustment, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, + holidays, workingDays, calendarInstanceForInterestRecalculation); } return changedTransactionDetail; } - public ChangedTransactionDetail undoWrittenOff(final List existingTransactionIds, final List existingReversedTransactionIds) { + public ChangedTransactionDetail undoWrittenOff(final List existingTransactionIds, + final List existingReversedTransactionIds, final boolean isHolidayEnabled, final List holidays, + final WorkingDays workingDays, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, + final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); existingTransactionIds.addAll(findExistingTransactionIds()); @@ -2606,6 +2670,10 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + } ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); @@ -2672,12 +2740,13 @@ public class Loan extends AbstractPersistable { final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final AppUser currentUser, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, - final WorkingDays workingDays) { + final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor); + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, + calendarInstanceForInterestRecalculation); validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING); @@ -2739,14 +2808,18 @@ public class Loan extends AbstractPersistable { private ChangedTransactionDetail closeDisbursements(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final CalendarInstance calendarInstanceForInterestRecalculation) { ChangedTransactionDetail changedTransactionDetail = null; if (isDisbursementAllowed() && atleastOnceDisbursed()) { this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount()); removeDisbursementDetail(); regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); - + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); @@ -2776,7 +2849,7 @@ public class Loan extends AbstractPersistable { final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, - final WorkingDays workingDays) { + final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) { validateAccountStatus(LoanEvent.LOAN_CLOSED); @@ -2804,7 +2877,8 @@ public class Loan extends AbstractPersistable { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor); + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, + calendarInstanceForInterestRecalculation); LoanTransaction loanTransaction = null; if (isOpen()) { @@ -3829,7 +3903,7 @@ public class Loan extends AbstractPersistable { final JsonCommand command, final List existingTransactionIds, final List existingReversedTransactionIds, final Map actualChanges, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays) { + final List holidays, final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) { validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName); disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate()); @@ -3842,6 +3916,11 @@ public class Loan extends AbstractPersistable { regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, + isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + } + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); @@ -3916,4 +3995,157 @@ public class Loan extends AbstractPersistable { } return maturityDate; } + + public ChangedTransactionDetail recalculateSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory, + final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, + final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, + final CalendarInstance calendarInstanceForInterestRecalculation, final List existingTransactionIds, + final List existingReversedTransactionIds) { + existingTransactionIds.addAll(findExistingTransactionIds()); + existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); + + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation); + } + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory + .determineProcessor(this.transactionProcessingStrategy); + final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( + getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, + charges()); + for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { + mapEntry.getValue().updateLoan(this); + } + /*** + * Commented since throwing exception if external id present for one of + * the transactions. for this need to save the reversed transactions + * first and then new transactions. + */ + // this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values()); + + updateLoanSummaryDerivedFields(); + + return changedTransactionDetail; + + } + + private void regenerateRepaymentScheduleWithInterestRecalculation(final LoanScheduleGeneratorFactory loanScheduleFactory, + final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, + final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, + final CalendarInstance calendarInstanceForInterestRecalculation) { + + if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return; } + + final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); + final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); + + final RoundingMode roundingMode = RoundingMode.HALF_EVEN; + final MathContext mc = new MathContext(8, roundingMode); + + final Integer loanTermFrequency = this.termFrequency; + final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); + final List disbursementData = new ArrayList<>(); + for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) { + disbursementData.add(disbursementDetails.toData()); + } + final List loanVariationTermsData = new ArrayList<>(); + boolean isDefaultEmiAmountReq = true; + for (LoanTermVariations variationTerms : this.loanTermVariations) { + if (variationTerms.getTermType().isEMIAmountVariation()) { + if (variationTerms.getTermApplicableFrom().equals(this.getDisbursementDate().toDate())) { + isDefaultEmiAmountReq = false; + } + loanVariationTermsData.add(variationTerms.toData()); + } + } + if (isDefaultEmiAmountReq) { + LoanTermVariationsData data = new LoanTermVariationsData(null, + LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), this.getDisbursementDate(), this.fixedEmiAmount); + loanVariationTermsData.add(data); + } + + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, + loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), + calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, + this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, + loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails, + calendarInstanceForInterestRecalculation); + + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory + .determineProcessor(this.transactionProcessingStrategy); + + final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency, + loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays, retreiveListOfTransactionsPostDisbursement(), + loanRepaymentScheduleTransactionProcessor); + + updateLoanSchedule(loanSchedule); + LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(); + Set charges = this.charges(); + for (LoanCharge loanCharge : charges) { + if (!loanCharge.isDueAtDisbursement()) { + if (loanCharge.getDueLocalDate() == null || !lastRepaymentDate.isBefore(loanCharge.getDueLocalDate())) { + recalculateLoanCharge(loanCharge); + loanCharge.updateWaivedAmount(getCurrency()); + } else { + loanCharge.setActive(false); + } + } + } + updateLoanSummaryDerivedFields(); + } + + public Money fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory) { + Money amount = Money.zero(getCurrency()); + + if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { + final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); + final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); + + final RoundingMode roundingMode = RoundingMode.HALF_EVEN; + final MathContext mc = new MathContext(8, roundingMode); + + final Integer loanTermFrequency = this.termFrequency; + final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); + final List disbursementData = new ArrayList<>(); + for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) { + disbursementData.add(disbursementDetails.toData()); + } + final List loanVariationTermsData = new ArrayList<>(); + boolean isDefaultEmiAmountReq = true; + for (LoanTermVariations variationTerms : this.loanTermVariations) { + if (variationTerms.getTermType().isEMIAmountVariation()) { + if (variationTerms.getTermApplicableFrom().equals(this.getDisbursementDate().toDate())) { + isDefaultEmiAmountReq = false; + } + loanVariationTermsData.add(variationTerms.toData()); + } + } + if (isDefaultEmiAmountReq) { + LoanTermVariationsData data = new LoanTermVariationsData(null, + LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), this.getDisbursementDate(), + this.fixedEmiAmount); + loanVariationTermsData.add(data); + } + + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(null, loanTermFrequency, + loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null, getInArrearsTolerance(), + this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, + this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate()); + amount = loanScheduleGenerator.fetchPrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), loanApplicationTerms, + mc); + } else { + amount = this.getTotalOutstandingOnLoan(); + } + return amount; + } + + private Money getTotalOutstandingOnLoan() { + Money cumulativeOurstanding = Money.zero(loanCurrency()); + + for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { + cumulativeOurstanding = cumulativeOurstanding.plus(scheduledRepayment.getTotalOutstanding(getCurrency())); + } + return cumulativeOurstanding; + } } \ 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 c67c411e4..591ef3550 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 @@ -9,6 +9,7 @@ import java.math.BigDecimal; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; public interface LoanAccountDomainService { @@ -27,4 +28,6 @@ public interface LoanAccountDomainService { LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount, PaymentDetail paymentDetail, String noteText, String txnExternalId); + + LocalDate getCalculatedRepaymentsStartingFromDate(LocalDate actualDisbursementDate, Loan loan, CalendarInstance calendarInstance); } 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 89c506954..72db6bfe9 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 @@ -30,11 +30,18 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.mifosplatform.portfolio.account.domain.AccountTransferRepository; import org.mifosplatform.portfolio.account.domain.AccountTransferTransaction; +import org.mifosplatform.portfolio.calendar.domain.Calendar; +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.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.note.domain.Note; import org.mifosplatform.portfolio.note.domain.NoteRepository; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; @@ -57,6 +64,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final NoteRepository noteRepository; private final AccountTransferRepository accountTransferRepository; + private final LoanScheduleGeneratorFactory loanScheduleFactory; + private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; + private final CalendarInstanceRepository calendarInstanceRepository; @Autowired public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, @@ -65,7 +75,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final WorkingDaysRepositoryWrapper workingDaysRepository, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final JournalEntryWritePlatformService journalEntryWritePlatformService, - final AccountTransferRepository accountTransferRepository) { + final AccountTransferRepository accountTransferRepository, final LoanScheduleGeneratorFactory loanScheduleFactory, + final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, + final CalendarInstanceRepository calendarInstanceRepository) { this.loanAccountAssembler = loanAccountAssembler; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -76,6 +88,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.accountTransferRepository = accountTransferRepository; + this.loanScheduleFactory = loanScheduleFactory; + this.applicationCurrencyRepository = applicationCurrencyRepository; + this.calendarInstanceRepository = calendarInstanceRepository; } @Transactional @@ -118,9 +133,28 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final WorkingDays workingDays = this.workingDaysRepository.findOne(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); + CalendarInstance restCalendarInstance = null; + ApplicationCurrency applicationCurrency = null; + LocalDate calculatedRepaymentsStartingFromDate = null; + boolean isHolidayEnabled = false; + 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 = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, + calendarInstance); + + isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + } + final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday, - holidays, workingDays, allowTransactionsOnNonWorkingDay, isRecoveryRepayment); + holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory, + applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); try { this.loanTransactionRepository.save(newRepaymentTransaction); @@ -324,6 +358,34 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { this.loanTransactionRepository.save(loanTransaction); } + @Override + public LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, + final CalendarInstance calendarInstance) { + final Calendar calendar = calendarInstance == null ? null : calendarInstance.getCalendar(); + LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate(); + if (calendar != null) {// sync repayments + + // TODO: AA - user provided first repayment date takes precedence + // over recalculated meeting date + if (calculatedRepaymentsStartingFromDate == null) { + // FIXME: AA - Possibility of having next meeting date + // immediately after disbursement date, + // need to have minimum number of days gap between disbursement + // and first repayment date. + final LoanProductRelatedDetail repaymentScheduleDetails = loan.repaymentScheduleDetail(); + if (repaymentScheduleDetails != null) {// Not expecting to be + // null + final Integer repayEvery = repaymentScheduleDetails.getRepayEvery(); + final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails + .getRepaymentPeriodFrequencyType()); + calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate, + repayEvery, frequency); + } + } + } + return calculatedRepaymentsStartingFromDate; + } + 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/domain/LoanCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java index fc5b70bac..fd137e02c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanCharge.java @@ -8,10 +8,12 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; +import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -41,6 +43,7 @@ import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode; import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; import org.mifosplatform.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; +import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidDetail; import org.springframework.data.jpa.domain.AbstractPersistable; @Entity @@ -695,7 +698,12 @@ public class LoanCharge extends AbstractPersistable { amountPaidToDate = amountPaidToDate.plus(amountOutstanding); this.amountPaid = amountPaidToDate.getAmount(); this.amountOutstanding = BigDecimal.ZERO; - this.paid = true; + Money waivedAmount = Money.of(processAmount.getCurrency(), this.amountWaived); + if (waivedAmount.isGreaterThanZero()) { + this.waived = true; + } else { + this.paid = true; + } } else { amountPaidOnThisCharge = processAmount; @@ -800,14 +808,18 @@ public class LoanCharge extends AbstractPersistable { return this.loanInstallmentCharge; } - public LoanRepaymentScheduleInstallment fetchRepaymentInstallment(final Money trasferedAmount) { + public List fetchRepaymentInstallment(final MonetaryCurrency currency) { + List chargePaidDetails = new ArrayList<>(); for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) { if (loanChargePerInstallment.isPending() - && trasferedAmount.getAmount().equals( - loanChargePerInstallment.getAmountThroughChargePayment(trasferedAmount.getCurrency()).getAmount())) { return loanChargePerInstallment - .getRepaymentInstallment(); } + && loanChargePerInstallment.getAmountThroughChargePayment(currency).isGreaterThanZero()) { + LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail( + loanChargePerInstallment.getAmountThroughChargePayment(currency), + loanChargePerInstallment.getRepaymentInstallment(), isFeeCharge()); + chargePaidDetails.add(chargePaidDetail); + } } - return null; + return chargePaidDetails; } public boolean isActive() { @@ -842,4 +854,37 @@ public class LoanCharge extends AbstractPersistable { public void updateOverdueInstallmentCharge(LoanOverdueInstallmentCharge overdueInstallmentCharge) { this.overdueInstallmentCharge = overdueInstallmentCharge; } + + public void updateWaivedAmount(MonetaryCurrency currency) { + if (isInstalmentFee()) { + for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) { + final Money amountWaived = chargePerInstallment.updateWaivedAmount(currency); + if (this.amountWaived == null) { + this.amountWaived = BigDecimal.ZERO; + } + this.amountWaived = this.amountWaived.add(amountWaived.getAmount()); + this.amountOutstanding = this.amountOutstanding.subtract(amountWaived.getAmount()); + if (determineIfFullyPaid()) { + this.paid = false; + this.waived = true; + } + return; + } + } + + Money waivedAmount = Money.of(currency, this.amountWaived); + if (waivedAmount.isGreaterThanZero()) { + + if (waivedAmount.isGreaterThan(this.getAmount(currency))) { + this.amountWaived = this.getAmount(currency).getAmount(); + this.amountOutstanding = BigDecimal.ZERO; + this.paid = false; + this.waived = true; + } else if (waivedAmount.isLessThan(this.getAmount(currency))) { + this.paid = false; + this.waived = false; + } + } + + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInstallmentCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInstallmentCharge.java index 224307cbe..18590c8cc 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInstallmentCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInstallmentCharge.java @@ -153,7 +153,7 @@ public class LoanInstallmentCharge extends AbstractPersistable { Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid); final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding); - + Money amountPaidPreviously = amountPaidToDate; Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency()); if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) { amountPaidOnThisCharge = amountOutstanding; @@ -166,8 +166,20 @@ public class LoanInstallmentCharge extends AbstractPersistable { this.amountPaid = amountPaidToDate.getAmount(); this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency()); } - this.amountThroughChargePayment = feeAmount.getAmount(); - this.paid = determineIfFullyPaid(); + Money amountFromChargePayment = Money.of(incrementBy.getCurrency(), this.amountThroughChargePayment); + if (amountPaidPreviously.isGreaterThanZero()) { + amountFromChargePayment = amountFromChargePayment.plus(feeAmount); + } else { + amountFromChargePayment = feeAmount; + } + this.amountThroughChargePayment = amountFromChargePayment.getAmount(); + if (determineIfFullyPaid()) { + if (this.amountWaived.compareTo(BigDecimal.ZERO) == 1) { + this.waived = true; + } else { + this.paid = true; + } + } return amountPaidOnThisCharge; } @@ -195,4 +207,20 @@ public class LoanInstallmentCharge extends AbstractPersistable { return Money.of(currency, this.amountThroughChargePayment); } + public Money updateWaivedAmount(final MonetaryCurrency currency) { + if (this.amountWaived.compareTo(BigDecimal.ZERO) == 1) { + int comparedValue = this.amountWaived.compareTo(this.getAmount()); + if (comparedValue == 1) { + this.amountWaived = this.getAmount(); + this.amountOutstanding = BigDecimal.ZERO; + this.paid = false; + this.waived = true; + } else if (comparedValue == -1) { + this.paid = false; + this.waived = false; + } + } + return getAmountWaived(currency); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java index eabdda55b..ddd058a42 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java @@ -26,6 +26,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_loan_recalculation_details") public class LoanInterestRecalculationDetails extends AbstractPersistable { + @SuppressWarnings("unused") @OneToOne @JoinColumn(name = "loan_id", nullable = false) private Loan loan; @@ -65,4 +66,12 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable this.rescheduleStrategyMethod = rescheduleStrategyMethod; } + public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() { + return InterestRecalculationCompoundingMethod.fromInt(this.interestRecalculationCompoundingMethod); + } + + public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() { + return LoanRescheduleStrategyMethod.fromInt(this.rescheduleStrategyMethod); + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index d63a6aa79..5b9a1f44b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -532,4 +532,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus this.fromDate = newFromDate.toDate(); } } + + public Money getTotalPaidInAdvance(final MonetaryCurrency currency) { + return Money.of(currency, this.totalPaidInAdvance); + } + + public Money getTotalPaidLate(final MonetaryCurrency currency) { + return Money.of(currency, this.totalPaidLate); + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java index 766f0cab0..f2b6c0577 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTransaction.java @@ -527,4 +527,8 @@ public final class LoanTransaction extends AbstractPersistable { this.externalId = externalId; } + public boolean isAccrual() { + return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed(); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 04f7ac051..cefc36ead 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -6,13 +6,16 @@ package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidDetail; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanChargePaidBy; @@ -69,36 +72,47 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen final List transactionstoBeProcessed = new ArrayList<>(); for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { if (loanTransaction.isChargePayment()) { - final List chargePaymentInstallments = new ArrayList<>(); + List chargePaidDetails = new ArrayList<>(); final Set chargePaidBies = loanTransaction.getLoanChargesPaid(); final Set transferCharges = new HashSet<>(); for (final LoanChargePaidBy chargePaidBy : chargePaidBies) { LoanCharge loanCharge = chargePaidBy.getLoanCharge(); transferCharges.add(loanCharge); if (loanCharge.isInstalmentFee()) { - LoanRepaymentScheduleInstallment installment = null; - if (loanCharge.isFeeCharge()) { - installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getFeeChargesPortion(currency)); - } else { - installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getPenaltyChargesPortion(currency)); - } - if (installment != null) { - chargePaymentInstallments.add(installment); - } + chargePaidDetails.addAll(loanCharge.fetchRepaymentInstallment(currency)); } } LocalDate startDate = disbursementDate; for (final LoanRepaymentScheduleInstallment installment : installments) { for (final LoanCharge loanCharge : transferCharges) { if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) { - chargePaymentInstallments.add(installment); + LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail( + installment.getFeeChargesOutstanding(currency), installment, loanCharge.isFeeCharge()); + chargePaidDetails.add(chargePaidDetail); break; } } startDate = installment.getDueDate(); } loanTransaction.resetDerivedComponents(); - handleTransaction(loanTransaction, currency, chargePaymentInstallments, transferCharges); + Money unprocessed = loanTransaction.getAmount(currency); + for (LoanChargePaidDetail chargePaidDetail : chargePaidDetails) { + final List processInstallments = new ArrayList<>(1); + unprocessed = unprocessed.minus(chargePaidDetail.getAmount()); + processInstallments.add(chargePaidDetail.getInstallment()); + Money unprocessedAfterProcess = handleTransactionAndCharges(loanTransaction, currency, processInstallments, + transferCharges, chargePaidDetail.getAmount(), chargePaidDetail.isFeeCharge()); + unprocessed = unprocessed.plus(unprocessedAfterProcess); + if (!unprocessed.isGreaterThanZero()) { + break; + } + } + + if (unprocessed.isGreaterThanZero()) { + onLoanOverpayment(loanTransaction, unprocessed); + loanTransaction.updateOverPayments(unprocessed); + } + } else { transactionstoBeProcessed.add(loanTransaction); } @@ -152,12 +166,73 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen public void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency, final List installments, final Set charges) { - // find earliest unpaid installment for which to apply this transaction + final Money amountToProcess = null; + final boolean isChargeAmount = false; + handleTransaction(loanTransaction, currency, installments, charges, amountToProcess, isChargeAmount); + + } + + private void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency, + final List installments, final Set charges, final Money chargeAmountToProcess, + final boolean isFeeCharge) { + + Money transactionAmountUnprocessed = handleTransactionAndCharges(loanTransaction, currency, installments, charges, + chargeAmountToProcess, isFeeCharge); + + if (transactionAmountUnprocessed.isGreaterThanZero()) { + if (loanTransaction.isWaiver()) { + loanTransaction.updateComponentsAndTotal(transactionAmountUnprocessed.zero(), transactionAmountUnprocessed.zero(), + transactionAmountUnprocessed.zero(), transactionAmountUnprocessed.zero()); + } else { + onLoanOverpayment(loanTransaction, transactionAmountUnprocessed); + loanTransaction.updateOverPayments(transactionAmountUnprocessed); + } + } + } + + private Money handleTransactionAndCharges(final LoanTransaction loanTransaction, final MonetaryCurrency currency, + final List installments, final Set charges, final Money chargeAmountToProcess, + final boolean isFeeCharge) { // to. + Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, chargeAmountToProcess); + + final Set loanFees = extractFeeCharges(charges); + final Set loanPenalties = extractPenaltyCharges(charges); + Integer installmentNumber = null; + if (loanTransaction.isChargePayment() && installments.size() == 1) { + installmentNumber = installments.get(0).getInstallmentNumber(); + } + + if (loanTransaction.isNotWaiver()) { + Money feeCharges = loanTransaction.getFeeChargesPortion(currency); + Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency); + if (chargeAmountToProcess != null) { + if (isFeeCharge) { + feeCharges = chargeAmountToProcess; + } else { + penaltyCharges = chargeAmountToProcess; + } + } + if (feeCharges.isGreaterThanZero()) { + updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber); + } + + if (penaltyCharges.isGreaterThanZero()) { + updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber); + } + } + return transactionAmountUnprocessed; + } + + private Money processTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency, + final List installments, Money amountToProcess) { int installmentIndex = 0; final LocalDate transactionDate = loanTransaction.getTransactionDate(); Money transactionAmountUnprocessed = loanTransaction.getAmount(currency); + if (amountToProcess != null) { + transactionAmountUnprocessed = amountToProcess; + } for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { @@ -182,30 +257,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen installmentIndex++; } - - final Set loanFees = extractFeeCharges(charges); - final Set loanPenalties = extractPenaltyCharges(charges); - Integer installmentNumber = null; - if (loanTransaction.isChargePayment() && installments.size() == 1) { - installmentNumber = installments.get(0).getInstallmentNumber(); - } - - if (loanTransaction.isNotWaiver()) { - final Money feeCharges = loanTransaction.getFeeChargesPortion(currency); - if (feeCharges.isGreaterThanZero()) { - updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber); - } - - final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency); - if (penaltyCharges.isGreaterThanZero()) { - updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber); - } - } - - if (transactionAmountUnprocessed.isGreaterThanZero()) { - onLoanOverpayment(loanTransaction, transactionAmountUnprocessed); - loanTransaction.updateOverPayments(transactionAmountUnprocessed); - } + return transactionAmountUnprocessed; } private Set extractFeeCharges(final Set loanCharges) { @@ -367,4 +419,42 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { // empty implementation by default. } + + @Override + public Map handleRecalculation(final LocalDate disbursementDate, + final List transactionsPostDisbursement, final MonetaryCurrency currency, + final List installments, LoanRepaymentScheduleInstallment currentinstallment, + final Map recalculationDates) { + + for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { + currentInstallment.resetDerivedComponents(); + currentInstallment.updateDerivedFields(currency, disbursementDate); + } + return handleRepaymentSchedule(transactionsPostDisbursement, currency, installments, currentinstallment, recalculationDates); + } + + @Override + public Map handleRepaymentSchedule(final List transactionsPostDisbursement, + final MonetaryCurrency currency, final List installments, + final LoanRepaymentScheduleInstallment installment, final Map recalculationDates) { + Map unprocessedMap = new HashMap<>(); + Money currentInstallmentOutstanding = installment.getTotalOutstanding(currency); + for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { + Money amountToProcess = null; + Money unProcessed = processTransaction(loanTransaction, currency, installments, amountToProcess); + if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate()) + && recalculationDates.get(loanTransaction.getTransactionDate()).isBefore(installment.getDueDate())) { + Money earlyPayment = currentInstallmentOutstanding.minus(installment.getTotalOutstanding(currency)); + if (earlyPayment.isGreaterThanZero()) { + unprocessedMap.put(loanTransaction.getTransactionDate(), earlyPayment); + } + currentInstallmentOutstanding = installment.getTotalOutstanding(currency); + } + + if (unProcessed.isGreaterThanZero()) { + unprocessedMap.put(loanTransaction.getTransactionDate(), unProcessed); + } + } + return unprocessedMap; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java index 5b85ef517..587df39e6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/LoanRepaymentScheduleTransactionProcessor.java @@ -6,10 +6,12 @@ package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor; import java.util.List; +import java.util.Map; import java.util.Set; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -26,4 +28,10 @@ public interface LoanRepaymentScheduleTransactionProcessor { void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, List repaymentScheduleInstallments); + Map handleRecalculation(LocalDate disbursementDate, List transactionsPostDisbursement, + MonetaryCurrency currency, List installments, LoanRepaymentScheduleInstallment currentinstallment, Map recalculationDates); + + Map handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, + List installments, LoanRepaymentScheduleInstallment currentInstallment, Map recalculationDates); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java index 12ead008f..41a22d847 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java @@ -39,8 +39,14 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); if (loanTransaction.isChargesWaiver()) { - // zero this type of transaction and ignore it for now. - transactionAmountRemaining = Money.zero(currency); + penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, + loanTransaction.getPenaltyChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + + feeChargesPortion = currentInstallment + .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } else if (loanTransaction.isInterestWaiver()) { interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java index 47a8992bc..431f33cc0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java @@ -64,8 +64,14 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); if (loanTransaction.isChargesWaiver()) { - // zero this type of transaction and ignore it for now. - transactionAmountRemaining = Money.zero(currency); + penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, + loanTransaction.getPenaltyChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + + feeChargesPortion = currentInstallment + .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } else if (loanTransaction.isInterestWaiver()) { interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java index 9838c223a..f574654ac 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/MifosStyleLoanRepaymentScheduleTransactionProcessor.java @@ -78,8 +78,14 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends Abstrac Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); if (loanTransaction.isChargesWaiver()) { - // zero this type of transaction and ignore it for now. - transactionAmountRemaining = Money.zero(currency); + penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, + loanTransaction.getPenaltyChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + + feeChargesPortion = currentInstallment + .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } else if (loanTransaction.isInterestWaiver()) { interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java index 89fc19aa3..e2d692a31 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java @@ -64,8 +64,14 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); if (loanTransaction.isChargesWaiver()) { - // zero this type of transaction and ignore it for now. - transactionAmountRemaining = Money.zero(currency); + penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, + loanTransaction.getPenaltyChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion); + + feeChargesPortion = currentInstallment + .waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency)); + transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion); + } else if (loanTransaction.isInterestWaiver()) { interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); 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 640aa726d..bbcf5669e 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 @@ -9,8 +9,11 @@ import java.math.BigDecimal; import java.math.MathContext; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import org.joda.time.Days; import org.joda.time.LocalDate; @@ -19,8 +22,13 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementDisbursementDateException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; @@ -37,6 +45,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener public LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + final List diffAmt = null; + return generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, diffAmt); + } + + private LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency, + final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, + final List holidays, final WorkingDays workingDays, List diffAmt) { // 1. generate list of proposed schedule due dates final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays, @@ -66,7 +81,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Collection periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, loanApplicationTerms, chargesDueAtTimeOfDisbursement); - // 4. Determine the total interest owed over the full loan for FLAT interest method . + // 4. Determine the total interest owed over the full loan for FLAT + // interest method . Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged( this.paymentPeriodsInOneYearCalculator, mc); @@ -91,10 +107,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt)); outstandingBalance = outstandingBalance.zero().plus(disburseAmt); } + Money reducePrincipal = totalCumulativePrincipal.zero(); + Money fixedEmiAmount = totalCumulativePrincipal.zero(); while (!outstandingBalance.isZero()) { + isFirstRepayment = false; actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment); - isFirstRepayment = false; LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays); final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); @@ -127,12 +145,40 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate, loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); + Money balanceForcalculation = outstandingBalance; + if (reducePrincipal.isGreaterThanZero()) { + switch (loanApplicationTerms.getRescheduleStrategyMethod()) { + case REDUCE_EMI_AMOUNT: + loanApplicationTerms.setFixedEmiAmount(null); + outstandingBalance = outstandingBalance.minus(reducePrincipal); + balanceForcalculation = outstandingBalance; + totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal); + reducePrincipal = reducePrincipal.zero(); + break; + case REDUCE_NUMBER_OF_INSTALLMENTS: + loanApplicationTerms.setFixedEmiAmount(fixedEmiAmount.getAmount()); + outstandingBalance = outstandingBalance.minus(reducePrincipal); + balanceForcalculation = outstandingBalance; + totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal); + reducePrincipal = reducePrincipal.zero(); + break; + case RESCHEDULE_NEXT_REPAYMENTS: + balanceForcalculation = balanceForcalculation.minus(reducePrincipal); + break; + default: + break; + } + } + + if (!balanceForcalculation.isGreaterThanZero()) { + break; + } // 5 determine principal,interest of repayment period final PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, - daysInPeriodApplicableForInterest, outstandingBalance, loanApplicationTerms, periodNumber, mc); + daysInPeriodApplicableForInterest, balanceForcalculation, loanApplicationTerms, periodNumber, mc); if (loanApplicationTerms.getFixedEmiAmount() != null && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) { @@ -141,32 +187,102 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanApplicationTerms.getFixedEmiAmount()); } // update cumulative fields for principal & interest + Money interestForThisinstallment = principalInterestForThisPeriod.interest(); totalCumulativePrincipal = totalCumulativePrincipal.plus(principalInterestForThisPeriod.principal()); - totalCumulativeInterest = totalCumulativeInterest.plus(principalInterestForThisPeriod.interest()); + totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); // 6. update outstandingLoanBlance using correct 'principalDue' outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal()); + Money actualOutstandingbalance = outstandingBalance; Money feeChargesForInstallment = principalDisbursed.zero(); Money penaltyChargesForInstallment = principalDisbursed.zero(); + Money principalForThisPeriod = principalInterestForThisPeriod.principal(); + if (principalForThisPeriod.isGreaterThan(reducePrincipal)) { + principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal); + reducePrincipal = reducePrincipal.zero(); + } else { + reducePrincipal = reducePrincipal.minus(principalForThisPeriod); + principalForThisPeriod = principalForThisPeriod.zero(); + } + + if (periodNumber == 1) { + fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment); + } + + if (diffAmt != null && !diffAmt.isEmpty()) { + Money interestDueToLatePayment = totalCumulativeInterest.zero(); + Money interestReducedDueToEarlyPayment = totalCumulativeInterest.zero(); + for (RecalculationDetail detail : diffAmt) { + if (!detail.isLatePayment() && detail.getStartDate().isAfter(periodStartDate) + && !detail.getStartDate().isAfter(scheduledDueDate)) { + reducePrincipal = reducePrincipal.plus(detail.getAmount()); + int diffDays = Days.daysBetween(detail.getStartDate(), scheduledDueDate).getDays(); + if (diffDays > 0) { + reducePrincipal = reducePrincipal.minus(principalForThisPeriod); + } + if (reducePrincipal.isLessThanZero()) { + reducePrincipal = reducePrincipal.zero(); + } else { + Money actualOutstanding = outstandingBalance.minus(reducePrincipal); + principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod); + if (actualOutstanding.isLessThanZero()) { + principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding); + } + } + + interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.plus(loanApplicationTerms.interestRateFor( + this.paymentPeriodsInOneYearCalculator, mc, diffDays, detail.getAmount())); + + } else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate) + && periodStartDate.isBefore(LocalDate.now())) { + LocalDate fromDate = periodStartDate; + LocalDate toDate = scheduledDueDate; + if (!detail.getStartDate().isAfter(periodStartDate)) { + fromDate = detail.getStartDate(); + } + if (!detail.getToDate().isAfter(scheduledDueDate)) { + toDate = detail.getToDate(); + } + if (toDate.isAfter(LocalDate.now())) { + toDate = LocalDate.now(); + } + int diffDays = Days.daysBetween(fromDate, toDate).getDays(); + interestDueToLatePayment = interestDueToLatePayment.plus(loanApplicationTerms.interestRateFor( + this.paymentPeriodsInOneYearCalculator, mc, diffDays, detail.getAmount())); + } + } + + if (totalOutstandingInterestPaymentDueToGrace.isGreaterThanZero()) { + totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.plus(interestDueToLatePayment) + .minus(interestReducedDueToEarlyPayment); + } else { + totalCumulativeInterest = totalCumulativeInterest.plus(interestDueToLatePayment) + .minus(interestReducedDueToEarlyPayment); + interestForThisinstallment = interestForThisinstallment.plus(interestDueToLatePayment).minus( + interestReducedDueToEarlyPayment); + } + + } + actualOutstandingbalance = actualOutstandingbalance.minus(reducePrincipal); // 8. sum up real totalInstallmentDue from components - final Money totalInstallmentDue = principalInterestForThisPeriod.principal() // - .plus(principalInterestForThisPeriod.interest()) // + final Money totalInstallmentDue = principalForThisPeriod// + .plus(interestForThisinstallment) // .plus(feeChargesForInstallment) // .plus(penaltyChargesForInstallment); // 9. create repayment period from parts final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, periodStartDate, - scheduledDueDate, principalInterestForThisPeriod.principal(), outstandingBalance, - principalInterestForThisPeriod.interest(), feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue); + scheduledDueDate, principalForThisPeriod, actualOutstandingbalance, interestForThisinstallment, + feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue); periods.add(installment); // handle cumulative fields loanTermInDays += daysInPeriod; totalPrincipalExpected = totalPrincipalExpected.add(principalInterestForThisPeriod.principal().getAmount()); - totalInterestCharged = totalInterestCharged.add(principalInterestForThisPeriod.interest().getAmount()); + totalInterestCharged = totalInterestCharged.add(interestForThisinstallment.getAmount()); totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount()); @@ -198,6 +314,40 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } + if (diffAmt != null && !diffAmt.isEmpty() && !periodStartDate.isAfter(LocalDate.now())) { + Map processDetails = new TreeMap<>(); + for (RecalculationDetail detail : diffAmt) { + if (periodStartDate.isBefore(detail.getStartDate()) && detail.isLatePayment()) { + if (processDetails.containsKey(detail.getToDate())) { + RecalculationDetail recalculationDetail = processDetails.get(detail.getToDate()); + RecalculationDetail updatedDetail = new RecalculationDetail(recalculationDetail.isLatePayment(), + recalculationDetail.getStartDate(), recalculationDetail.getToDate(), recalculationDetail.getAmount().plus( + detail.getAmount())); + processDetails.put(updatedDetail.getToDate(), updatedDetail); + } else { + processDetails.put(detail.getToDate(), detail); + } + } + } + + for (RecalculationDetail detail : processDetails.values()) { + LocalDate fromDate = detail.getStartDate(); + LocalDate toDate = detail.getToDate(); + int diffDays = Days.daysBetween(fromDate, toDate).getDays(); + Money interestDueToLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, diffDays, + detail.getAmount()); + + totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount()); + totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount()); + + final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, toDate, + interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment, + interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment); + periods.add(installment); + periodNumber++; + } + } + return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected, totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding); @@ -366,4 +516,205 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return cumulative; } + + @Override + public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final ApplicationCurrency applicationCurrency, + final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, + final List holidays, final WorkingDays workingDays, final List transactions, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + + int installmentNumber = 0; + final List recalculationDetails = new ArrayList<>(); + LoanScheduleModel loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, + holidays, workingDays); + + while (installmentNumber < loanScheduleModel.getPeriods().size() - 1) { + RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges, + isHolidayEnabled, holidays, workingDays, transactions, loanRepaymentScheduleTransactionProcessor, installmentNumber, + loanScheduleModel, recalculationDetails); + installmentNumber = recalculatedSchedule.getInstallmentNumber(); + loanScheduleModel = recalculatedSchedule.getLoanScheduleModel(); + } + return loanScheduleModel; + + } + + 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, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber, + LoanScheduleModel model, final List recalculationDetails) { + boolean processRecalculate = false; + int processedInstallmentNumber = installmentNumber; + final List installments = new ArrayList<>(); + final List diffAmt = new ArrayList<>(); + Money unpaidPricipal = loanApplicationTerms.getPrincipal(); + for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) { + if (scheduledLoanInstallment.isRepaymentPeriod()) { + final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null, + scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), + scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), + scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), + scheduledLoanInstallment.penaltyChargesDue()); + installments.add(installment); + } + } + MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency(); + final List processinstallmets = new ArrayList<>(); + + for (LoanRepaymentScheduleInstallment installment : installments) { + processinstallmets.add(installment); + unpaidPricipal = unpaidPricipal.minus(installment.getPrincipal(currency)); + if (installment.getInstallmentNumber() <= installmentNumber) { + continue; + } + processedInstallmentNumber = installment.getInstallmentNumber(); + if (installment.getDueDate().isAfter(LocalDate.now())) { + continue; + } + List transactionsForInstallment = new ArrayList<>(); + Map recalculationDates = new HashMap<>(); + LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, + isHolidayEnabled, holidays, workingDays); + for (LoanTransaction loanTransaction : transactions) { + LocalDate loantransactionDate = loanTransaction.getTransactionDate(); + if (!loantransactionDate.isAfter(transactionsDate) && !loanTransaction.isAccrual()) { + transactionsForInstallment.add(loanTransaction); + recalculationDates.put( + loantransactionDate, + getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays, + workingDays)); + } + } + Map earlyPaymentMap = loanRepaymentScheduleTransactionProcessor.handleRecalculation( + loanApplicationTerms.getExpectedDisbursementDate(), transactionsForInstallment, currency, processinstallmets, + installment, recalculationDates); + + for (Map.Entry entry : earlyPaymentMap.entrySet()) { + LocalDate startDate = entry.getKey(); + if (unpaidPricipal.isGreaterThan(entry.getValue())) { + startDate = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays, + workingDays); + } + RecalculationDetail recalculationDetail = new RecalculationDetail(false, startDate, null, entry.getValue()); + if (!entry.getKey().isAfter(installment.getDueDate()) && entry.getKey().isAfter(installment.getFromDate())) { + diffAmt.add(recalculationDetail); + } + } + + if (installment.getDueDate().isBefore(LocalDate.now())) { + LocalDate startDate = installment.getDueDate(); + Money totalOutstanding = installment.getTotalOutstanding(currency); + + while (totalOutstanding.isGreaterThanZero() && startDate.isBefore(LocalDate.now())) { + LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, + holidays, workingDays); + LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, isHolidayEnabled, holidays, + workingDays); + applyRest(transactions, startDate, recalculateFrom, installment, loanRepaymentScheduleTransactionProcessor, currency, + loanApplicationTerms, isHolidayEnabled, holidays, workingDays); + totalOutstanding = installment.getTotalOutstanding(currency); + if (totalOutstanding.isGreaterThanZero()) { + Money latepaymentoutstanding = installment.getPrincipalOutstanding(currency); + switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) { + case INTEREST: + latepaymentoutstanding = latepaymentoutstanding.plus(installment.getInterestOutstanding(currency)); + break; + case INTEREST_AND_FEE: + latepaymentoutstanding = latepaymentoutstanding.plus(installment.getInterestOutstanding(currency)) + .plus(installment.getFeeChargesOutstanding(currency)) + .plus(installment.getPenaltyChargesOutstanding(currency)); + break; + case FEE: + latepaymentoutstanding = latepaymentoutstanding.plus(installment.getFeeChargesOutstanding(currency)).plus( + installment.getPenaltyChargesOutstanding(currency)); + break; + + default: + break; + } + RecalculationDetail recalculationDetail = new RecalculationDetail(true, recalculateFrom, recalcualteTill, + latepaymentoutstanding); + diffAmt.add(recalculationDetail); + } + startDate = recalcualteTill; + } + } + + if (!diffAmt.isEmpty()) { + processRecalculate = true; + break; + } + + } + if (processRecalculate) { + recalculationDetails.addAll(diffAmt); + model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, + recalculationDetails); + } + return new RecalculatedSchedule(model, processedInstallmentNumber); + } + + private void applyRest(List loanTransactions, LocalDate from, LocalDate to, + LoanRepaymentScheduleInstallment installment, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency, + final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, final List holidays, + final WorkingDays workingDays) { + List transactions = new ArrayList<>(); + Map recalculationDates = new HashMap<>(); + for (LoanTransaction transaction : loanTransactions) { + LocalDate loantransactionDate = transaction.getTransactionDate(); + if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to) && !transaction.isAccrual()) { + transactions.add(transaction); + recalculationDates.put( + loantransactionDate, + getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays, + workingDays)); + } + } + List installments = new ArrayList<>(1); + installments.add(installment); + loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(transactions, currency, installments, installment, + recalculationDates); + } + + @SuppressWarnings("unused") + private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, + final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); + LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate); + /* + * nextScheduleDate = + * this.scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, + * loanApplicationTerms, isHolidayEnabled, holidays, workingDays); + */ + return nextScheduleDate; + } + + @Override + public Money fetchPrepaymentAmount(final List installments, MonetaryCurrency currency, + final LoanApplicationTerms applicationTerms, final MathContext mc) { + Money prepaymentAmount = Money.zero(currency); + Money amount = Money.zero(currency); + LocalDate calculateInterestFrom = LocalDate.now(); + for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { + if (currentInstallment.isNotFullyPaidOff()) { + if (!currentInstallment.getDueDate().isAfter(LocalDate.now())) { + amount = amount.plus(currentInstallment.getTotalOutstanding(currency)); + } else { + prepaymentAmount = prepaymentAmount.plus(currentInstallment.getPrincipalOutstanding(currency)); + if (currentInstallment.getFromDate().isBefore(calculateInterestFrom)) { + calculateInterestFrom = currentInstallment.getFromDate(); + } + + } + } + } + + int days = Days.daysBetween(calculateInterestFrom, LocalDate.now()).getDays(); + Money interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, days, prepaymentAmount); + prepaymentAmount = prepaymentAmount.plus(interest).plus(amount); + return prepaymentAmount; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index a9d46e396..001e345fa 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -16,15 +16,19 @@ import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; 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.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; +import org.mifosplatform.portfolio.loanaccount.domain.LoanInterestRecalculationDetails; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; +import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; public final class LoanApplicationTerms { @@ -107,8 +111,14 @@ public final class LoanApplicationTerms { private final DaysInMonthType daysInMonthType; private final DaysInYearType daysInYearType; - - private final boolean isInterestRecalculationEnabled; + + private final boolean interestRecalculationEnabled; + + private final LoanRescheduleStrategyMethod rescheduleStrategyMethod; + + private final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod; + + private final CalendarInstance restCalendarInstance; public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, @@ -124,12 +134,16 @@ public final class LoanApplicationTerms { final List emiAmountVariations, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled) { + final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; + final CalendarInstance restCalendarInstance = null; return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, - maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled); + maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, + isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -160,6 +174,9 @@ public final class LoanApplicationTerms { final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); + final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; + final CalendarInstance restCalendarInstance = null; return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, @@ -167,7 +184,54 @@ public final class LoanApplicationTerms { expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(), - daysInMonthType, daysInYearType, isInterestRecalculationEnabled); + daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, + interestRecalculationCompoundingMethod, restCalendarInstance); + } + + 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 List emiAmountVariations, final LocalDate interestChargedFromDate, + final LoanInterestRecalculationDetails interestRecalculationDetails, final CalendarInstance restCalendarInstance) { + + 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 BigDecimal annualNominalInterestRate = loanProductRelatedDetail.getAnnualNominalInterestRate(); + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail + .getInterestCalculationPeriodMethod(); + final Money principalMoney = loanProductRelatedDetail.getPrincipal(); + + // + final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment(); + 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(); + } + + return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, + repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, + interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, + expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, + graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, + disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(), + daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, + interestRecalculationCompoundingMethod, restCalendarInstance); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -182,7 +246,8 @@ public final class LoanApplicationTerms { final boolean multiDisburseLoan, final BigDecimal emiAmount, final List disbursementDatas, final BigDecimal maxOutstandingBalance, final List emiAmountVariations, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, - final boolean isInterestRecalculationEnabled) { + final boolean isInterestRecalculationEnabled, final LoanRescheduleStrategyMethod rescheduleStrategyMethod, + final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod, final CalendarInstance restCalendarInstance) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; @@ -216,7 +281,10 @@ public final class LoanApplicationTerms { this.graceOnArrearsAgeing = graceOnArrearsAgeing; this.daysInMonthType = daysInMonthType; this.daysInYearType = daysInYearType; - this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; + this.interestRecalculationEnabled = isInterestRecalculationEnabled; + this.rescheduleStrategyMethod = rescheduleStrategyMethod; + this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; + this.restCalendarInstance = restCalendarInstance; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -593,6 +661,18 @@ public final class LoanApplicationTerms { return periodicInterestRate; } + public Money interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, int days, + final Money outstandingBalance) { + final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); + + final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); + final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); + final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide( + divisor, mc); + BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(days), mc); + return outstandingBalance.multiplyRetainScale(interestRate, mc.getRoundingMode()); + } + private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) { long periodsInOneYear = calculator.calculate(this.repaymentPeriodFrequencyType).longValue(); @@ -663,7 +743,7 @@ public final class LoanApplicationTerms { private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) { - if (multiDisburseLoan && fixedEmiAmount != null) { return fixedEmiAmount.doubleValue(); } + if (fixedEmiAmount != null) { return fixedEmiAmount.doubleValue(); } final double futureValue = 0; final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue(); @@ -762,7 +842,7 @@ public final class LoanApplicationTerms { this.interestCalculationPeriodMethod, this.repaymentEvery, this.repaymentPeriodFrequencyType, this.numberOfRepayments, this.principalGrace, this.interestPaymentGrace, this.interestChargingGrace, this.amortizationMethod, this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), - this.daysInYearType.getValue(), this.isInterestRecalculationEnabled); + this.daysInYearType.getValue(), this.interestRecalculationEnabled); } public Integer getLoanTermFrequency() { @@ -863,4 +943,28 @@ public final class LoanApplicationTerms { } } } + + public void setFixedEmiAmount(BigDecimal fixedEmiAmount) { + this.fixedEmiAmount = fixedEmiAmount; + } + + public LoanRescheduleStrategyMethod getLoanRescheduleStrategyMethod() { + return LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT; + } + + public boolean isInterestRecalculationEnabled() { + return this.interestRecalculationEnabled; + } + + public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() { + return this.rescheduleStrategyMethod; + } + + public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() { + return this.interestRecalculationCompoundingMethod; + } + + public CalendarInstance getRestCalendarInstance() { + return this.restCalendarInstance; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java index 594d88d9a..1407b4f1e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java @@ -11,11 +11,24 @@ import java.util.Set; import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; public interface LoanScheduleGenerator { LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms, Set loanCharges, boolean isHolidayEnabled, List holidays, final WorkingDays workingDays); + + LoanScheduleModel rescheduleNextInstallments(MathContext mc, ApplicationCurrency applicationCurrency, + LoanApplicationTerms loanApplicationTerms, Set loanCharges, boolean isHolidayEnabled, List holidays, + WorkingDays workingDays, List transactions, + LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); + + Money fetchPrepaymentAmount(List installments, MonetaryCurrency currency, + LoanApplicationTerms applicationTerms, MathContext mc); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculatedSchedule.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculatedSchedule.java new file mode 100755 index 000000000..f458cfab0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculatedSchedule.java @@ -0,0 +1,20 @@ +package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; + +public class RecalculatedSchedule { + + private final LoanScheduleModel loanScheduleModel; + private final Integer installmentNumber; + + public RecalculatedSchedule(final LoanScheduleModel loanScheduleModel, final Integer installmentNumber) { + this.loanScheduleModel = loanScheduleModel; + this.installmentNumber = installmentNumber; + } + + public LoanScheduleModel getLoanScheduleModel() { + return this.loanScheduleModel; + } + + public Integer getInstallmentNumber() { + return this.installmentNumber; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java new file mode 100755 index 000000000..a1e91799d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/RecalculationDetail.java @@ -0,0 +1,44 @@ +package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; + +import org.joda.time.LocalDate; +import org.mifosplatform.organisation.monetary.domain.Money; + +public class RecalculationDetail { + + private boolean isLatePayment; + private LocalDate startDate; + private LocalDate toDate; + private Money amount; + + public RecalculationDetail(final boolean isLatePayment, final LocalDate startDate, final LocalDate toDate, final Money amount) { + this.isLatePayment = isLatePayment; + this.startDate = startDate; + this.toDate = toDate; + this.amount = amount; + } + + public boolean isLatePayment() { + return this.isLatePayment; + } + + public LocalDate getStartDate() { + return this.startDate; + } + + public LocalDate getToDate() { + return this.toDate; + } + + public Money getAmount() { + return this.amount; + } + + public boolean isOverlapping(LocalDate startDate, LocalDate endDate) { + return !this.startDate.isAfter(endDate) && !startDate.isAfter(this.toDate); + } + + + public void updateAmount(Money amount) { + this.amount = amount; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java index 5e7d930af..533a611e2 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -87,4 +87,8 @@ public interface LoanReadPlatformService { LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId); Collection retrivePeriodicAccrualData(LocalDate tillDate); + + Collection fetchArrearLoans(); + + LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId); } \ No newline at end of file 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 3e021a86a..9299d808e 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 @@ -81,6 +81,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFound import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.mifosplatform.portfolio.loanproduct.service.LoanDropdownReadPlatformService; @@ -120,6 +121,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private final LoanMapper loaanLoanMapper = new LoanMapper(); private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd"); + private final LoanScheduleGeneratorFactory loanScheduleFactory; @Autowired public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository, @@ -129,7 +131,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final GroupReadPlatformService groupReadPlatformService, final LoanDropdownReadPlatformService loanDropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService, final CodeValueReadPlatformService codeValueReadPlatformService, final RoutingDataSource dataSource, - final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService) { + final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService, + final LoanScheduleGeneratorFactory loanScheduleFactory) { this.context = context; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -145,6 +148,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.staffReadPlatformService = staffReadPlatformService; this.jdbcTemplate = new JdbcTemplate(dataSource); this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); + this.loanScheduleFactory = loanScheduleFactory; } @Override @@ -364,6 +368,29 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, paymentOptions, null, null, null); } + @Override + public LoanTransactionData retrieveLoanPrePaymentTemplate(final Long loanId) { + + this.context.authenticatedUser(); + + final Loan loan = this.loanRepository.findOne(loanId); + if (loan == null) { throw new LoanNotFoundException(loanId); } + + final MonetaryCurrency currency = loan.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + + final CurrencyData currencyData = applicationCurrency.toData(); + + final LocalDate earliestUnpaidInstallmentDate = LocalDate.now(); + + final Money precloseAmount = loan.fetchPrepaymentDetail(this.loanScheduleFactory); + final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT); + final Collection paymentOptions = this.codeValueReadPlatformService + .retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName); + return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, + precloseAmount.getAmount(), null, null, null, null, null, paymentOptions, null, null, null); + } + @Override public LoanTransactionData retrieveWaiveInterestDetails(final Long loanId) { @@ -1533,4 +1560,22 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { loan.getTotalOutstandingAmount(), null, null, null, null, null, null, null, null); } + @Override + public Collection fetchArrearLoans() { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("SELECT ml.id FROM m_loan ml "); + sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "); + sqlBuilder.append(" WHERE ml.loan_status_id = ? "); + sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 "); + sqlBuilder.append(" and ml.is_npa = 0 "); + sqlBuilder.append(" and mr.completed_derived is false "); + sqlBuilder.append(" and mr.duedate < ? "); + sqlBuilder.append(" group by ml.id"); + try { + return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()) }); + } catch (final EmptyResultDataAccessException e) { + return null; + } + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java index 79408718d..93e3151dd 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -79,4 +79,6 @@ public interface LoanWritePlatformService { CommandProcessingResult updateDisbursementDateForTranche(Long loanId, Long disbursementId, JsonCommand command); + void recalculateInterest(); + } \ No newline at end of file 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 4b05334f6..77cef2398 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 @@ -61,7 +61,6 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarType; -import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode; import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; @@ -115,7 +114,6 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.ScheduledDate import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator; import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; -import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException; import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService; @@ -237,7 +235,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf checkForProductMixRestrictions(loan); // validate actual disbursement date against meeting date - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); if (loan.isSyncDisbursementWithMeeting()) { @@ -257,8 +255,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf // Recalculate first repayment date based in actual disbursement date. final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); - final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(actualDisbursementDate, loan, - calendarInstance); + final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + actualDisbursementDate, loan, calendarInstance); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), actualDisbursementDate.toDate()); @@ -282,9 +280,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.getLoanTransactions().add(disbursementTransaction); } + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } + loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, - allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule); + allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance); } if (!changes.isEmpty()) { saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -333,7 +337,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } - + updateRecurringCalendarDatesForInterestRecalculation(loan); return new CommandProcessingResultBuilder() // @@ -376,33 +380,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - private LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, - final CalendarInstance calendarInstance) { - final Calendar calendar = calendarInstance == null ? null : calendarInstance.getCalendar(); - LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate(); - if (calendar != null) {// sync repayments - - // TODO: AA - user provided first repayment date takes precedence - // over recalculated meeting date - if (calculatedRepaymentsStartingFromDate == null) { - // FIXME: AA - Possibility of having next meeting date - // immediately after disbursement date, - // need to have minimum number of days gap between disbursement - // and first repayment date. - final LoanProductRelatedDetail repaymentScheduleDetails = loan.repaymentScheduleDetail(); - if (repaymentScheduleDetails != null) {// Not expecting to be - // null - final Integer repayEvery = repaymentScheduleDetails.getRepayEvery(); - final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails - .getRepaymentPeriodFrequencyType()); - calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate, - repayEvery, frequency); - } - } - } - return calculatedRepaymentsStartingFromDate; - } - /**** * TODO Vishwas: Pair with Ashok and re-factor collection sheet code-base * @@ -462,9 +439,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf disbursementTransaction.updateLoan(loan); loan.getLoanTransactions().add(disbursementTransaction); } + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } + loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays, - allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule); + allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance); } if (!changes.isEmpty()) { try { @@ -541,11 +524,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final MonetaryCurrency currency = loan.getCurrency(); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); final LocalDate actualDisbursementDate = loan.getDisbursementDate(); - final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(actualDisbursementDate, loan, - calendarInstance); + final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + actualDisbursementDate, loan, calendarInstance); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), @@ -699,9 +682,28 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final WorkingDays workingDays = this.workingDaysRepository.findOne(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); + CalendarInstance restCalendarInstance = null; + ApplicationCurrency applicationCurrency = null; + LocalDate calculatedRepaymentsStartingFromDate = null; + boolean isHolidayEnabled = false; + 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(); + } + final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, - allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay); + allowTransactionsOnHoliday, holidays, workingDays, isHolidayEnabled, allowTransactionsOnNonWorkingDay, + this.loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { if (paymentDetail != null) { @@ -783,8 +785,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, transactionDate); - final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, - defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds); + CalendarInstance restCalendarInstance = null; + ApplicationCurrency applicationCurrency = null; + LocalDate calculatedRepaymentsStartingFromDate = null; + List holidays = null; + boolean isHolidayEnabled = false; + WorkingDays workingDays = 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(); + } + + final ChangedTransactionDetail changedTransactionDetail = loan + .waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, + existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, this.loanScheduleFactory, + applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); this.loanTransactionRepository.save(waiveInterestTransaction); @@ -844,10 +870,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); - final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, - calendarInstance); + final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + loan.getDisbursementDate(), loan, calendarInstance); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan .getDisbursementDate().toDate()); @@ -857,9 +883,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final MonetaryCurrency currency = loan.getCurrency(); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } + final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), changes, existingTransactionIds, existingReversedTransactionIds, currentUser, this.loanScheduleFactory, - applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance); LoanTransaction writeoff = changedTransactionDetail.getNewTransactionMappings().remove(0L); this.loanTransactionRepository.save(writeoff); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { @@ -906,10 +938,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); - final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, - calendarInstance); + final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + loan.getDisbursementDate(), loan, calendarInstance); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan .getDisbursementDate().toDate()); @@ -919,9 +951,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final MonetaryCurrency currency = loan.getCurrency(); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + + } ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, existingTransactionIds, existingReversedTransactionIds, this.loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance); final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); if (possibleClosingTransaction != null) { this.loanTransactionRepository.save(possibleClosingTransaction); @@ -1429,9 +1467,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate); loan.getLoanTransactions().add(newTransferAcceptanceTransaction); - if(loan.getTotalOverpaid() !=null){ + if (loan.getTotalOverpaid() != null) { loan.setLoanStatus(LoanStatus.OVERPAID.getValue()); - }else{ + } else { loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); } if (loanOfficer != null) { @@ -1953,7 +1991,31 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (!loan.isClosedWrittenOff()) { throw new PlatformServiceUnavailableException( "error.msg.loan.status.not.written.off.update.not.allowed", "Loan :" + loanId + " update not allowed as loan status is not written off", loanId); } - ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds); + CalendarInstance restCalendarInstance = null; + ApplicationCurrency applicationCurrency = null; + LocalDate calculatedRepaymentsStartingFromDate = null; + List holidays = null; + boolean isHolidayEnabled = false; + WorkingDays workingDays = 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(); + } + + ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, + isHolidayEnabled, holidays, workingDays, this.loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, restCalendarInstance); if (changedTransactionDetail != null) { for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { this.loanTransactionRepository.save(mapEntry.getValue()); @@ -1979,10 +2041,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf checkClientOrGroupActive(loan); LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId); this.loanEventApiJsonValidator.validateUpdateDisbursementDate(command.json(), loanDisbursementDetails); - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); - final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, - calendarInstance); + final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + loan.getDisbursementDate(), loan, calendarInstance); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan .getDisbursementDate().toDate()); @@ -1993,9 +2055,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); final Map changes = new LinkedHashMap<>(); + CalendarInstance restCalendarInstance = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + } final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command, existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance); this.loanRepository.saveAndFlush(loan); @@ -2085,6 +2152,56 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return transaction; } + @Override + @CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN) + public void recalculateInterest() { + Collection loanIds = this.loanReadPlatformService.fetchArrearLoans(); + for (Long loanId : loanIds) { + recalculateInterest(loanId); + } + } + + @Transactional + public void recalculateInterest(final long loanId) { + Loan loan = this.loanAssembler.assembleFrom(loanId); + CalendarInstance restCalendarInstance = null; + ApplicationCurrency applicationCurrency = null; + LocalDate calculatedRepaymentsStartingFromDate = null; + List holidays = null; + boolean isHolidayEnabled = false; + WorkingDays workingDays = null; + final List existingTransactionIds = new ArrayList<>(); + final List existingReversedTransactionIds = new ArrayList<>(); + 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(); + } + ChangedTransactionDetail changedTransactionDetail = loan.recalculateSchedule(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance, + existingTransactionIds, existingReversedTransactionIds); + + this.loanRepository.save(loan); + + if (changedTransactionDetail != null) { + for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { + this.loanTransactionRepository.save(mapEntry.getValue()); + this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); + } + } + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + } + private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); if (transferTransaction != null) {