From 659ffa3e9ad0a21661fd597f910dde18a6de9684 Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Sun, 26 Oct 2014 13:47:48 +0530 Subject: [PATCH] MIFOSX-1586 : corrected overdue charges with interest calculation --- .../ClientLoanIntegrationTest.java | 166 ++++- .../common/charges/ChargesHelper.java | 12 + ...AccountMappingFromApiJsonDeserializer.java | 2 +- .../loanaccount/data/HolidayDetailDTO.java | 52 ++ .../data/ScheduleGeneratorDTO.java | 97 +++ .../portfolio/loanaccount/domain/Loan.java | 568 +++++++++++------- .../domain/LoanAccountDomainServiceJpa.java | 50 +- .../loanaccount/domain/LoanCharge.java | 4 + .../LoanInterestRecalculationDetails.java | 1 + .../domain/LoanOverdueInstallmentCharge.java | 22 +- .../LoanRepaymentScheduleInstallment.java | 129 ++-- ...oanRepaymentScheduleProcessingWrapper.java | 31 +- .../loanaccount/domain/LoanTransaction.java | 9 +- ...RepaymentScheduleTransactionProcessor.java | 28 +- ...RepaymentScheduleTransactionProcessor.java | 14 +- .../domain/AbstractLoanScheduleGenerator.java | 276 ++++++--- .../domain/DefaultScheduledDateGenerator.java | 25 +- .../domain/LoanApplicationTerms.java | 81 +-- .../domain/LoanScheduleGenerator.java | 27 +- .../domain/LoanScheduleModel.java | 14 + .../domain/ScheduledDateGenerator.java | 13 +- .../service/LoanScheduleAssembler.java | 29 +- ...cheduleCalculationPlatformServiceImpl.java | 2 +- .../domain/DefaultLoanReschedulerFactory.java | 53 +- .../domain/LoanReschedulerFactory.java | 12 +- ...nReschedulePreviewPlatformServiceImpl.java | 4 +- ...heduleRequestWritePlatformServiceImpl.java | 4 +- .../LoanAccrualWritePlatformServiceImpl.java | 20 +- ...WritePlatformServiceJpaRepositoryImpl.java | 397 ++++++++---- .../loanproduct/data/LoanOverdueDTO.java | 22 + ...nterestRecalculationCompoundingMethod.java | 5 + 31 files changed, 1520 insertions(+), 649 deletions(-) create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/HolidayDetailDTO.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java create mode 100755 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanOverdueDTO.java diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java index 95d51c750..6b947db27 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java @@ -2444,11 +2444,15 @@ public class ClientLoanIntegrationTest { loanCharges = this.loanTransactionHelper.getLoanCharges(loanID); validateCharge(flatInstallmentFee, loanCharges, "50", "100.00", "50.0", "50.0"); - /*this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry(Float.valueOf("50.0"), - JournalEntry.TransactionType.CREDIT)); - this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011", - new JournalEntry(Float.valueOf("50.0"), JournalEntry.TransactionType.DEBIT)); -*/ + /* + * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, + * "20 September 2011", new JournalEntry(Float.valueOf("50.0"), + * JournalEntry.TransactionType.CREDIT)); + * this.journalEntryHelper.checkJournalEntryForExpenseAccount + * (expenseAccount, "20 September 2011", new + * JournalEntry(Float.valueOf("50.0"), + * JournalEntry.TransactionType.DEBIT)); + */ final String jobName = "Add Accrual Transactions"; try { this.schedulerJobHelper.excuteJob(jobName); @@ -2645,10 +2649,15 @@ public class ClientLoanIntegrationTest { loanCharges = this.loanTransactionHelper.getLoanCharges(loanID); validateCharge(percentageInstallmentFee, loanCharges, "1", "61.19", "29.11", "29.70"); - /*this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry(Float.valueOf("29.7"), - JournalEntry.TransactionType.CREDIT)); - this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011", - new JournalEntry(Float.valueOf("29.7"), JournalEntry.TransactionType.DEBIT));*/ + /* + * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, + * "20 September 2011", new JournalEntry(Float.valueOf("29.7"), + * JournalEntry.TransactionType.CREDIT)); + * this.journalEntryHelper.checkJournalEntryForExpenseAccount + * (expenseAccount, "20 September 2011", new + * JournalEntry(Float.valueOf("29.7"), + * JournalEntry.TransactionType.DEBIT)); + */ final String jobName = "Add Accrual Transactions"; try { @@ -2850,10 +2859,15 @@ public class ClientLoanIntegrationTest { loanCharges = this.loanTransactionHelper.getLoanCharges(loanID); validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "63.02", "31.51", "31.51"); - /*this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry( - Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT)); - this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011", - new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.DEBIT));*/ + /* + * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, + * "20 September 2011", new JournalEntry( Float.valueOf("31.51"), + * JournalEntry.TransactionType.CREDIT)); + * this.journalEntryHelper.checkJournalEntryForExpenseAccount + * (expenseAccount, "20 September 2011", new + * JournalEntry(Float.valueOf("31.51"), + * JournalEntry.TransactionType.DEBIT)); + */ final String jobName = "Add Accrual Transactions"; try { @@ -3141,7 +3155,7 @@ public class ClientLoanIntegrationTest { final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, - LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null,null); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, new ArrayList(0)); @@ -3231,7 +3245,7 @@ public class ClientLoanIntegrationTest { final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, - LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE,null); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0)); @@ -3356,7 +3370,7 @@ public class ClientLoanIntegrationTest { final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE, LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS, - LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", REST_START_DATE,null); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", REST_START_DATE, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, charges); @@ -3433,6 +3447,113 @@ public class ClientLoanIntegrationTest { } + @Test + public void testLoanScheduleWithInterestRecalculation_WITH_REST_DAILY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_WITH_OVERDUE_CHARGE() + throws InterruptedException { + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + + Calendar todaysDate = Calendar.getInstance(); + todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 3); + final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime()); + todaysDate.add(Calendar.DAY_OF_MONTH, -2); + final String REST_START_DATE = dateFormat.format(todaysDate.getTime()); + + todaysDate = Calendar.getInstance(); + + Integer overdueFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec, + ChargesHelper.getLoanOverdueFeeJSONWithCalculattionTypePercentage()); + Assert.assertNotNull(overdueFeeChargeId); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, + LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE, + LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", REST_START_DATE, null, overdueFeeChargeId.toString()); + + final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, + REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, null); + + Assert.assertNotNull(loanID); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + ArrayList loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + List> expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(); + addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2505.73", "23.18", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2517.29", "11.62", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(); + addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2482.55", "46.36", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2482.33", "46.58", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2552.36", "11.78", "0.0", "0.0"); + + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + String JobName = "Apply penalty to overdue loans"; + this.schedulerJobHelper.excuteJob(JobName); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(); + addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "252.89"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2481.38", "47.53", "0.0", "252.89"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2479.99", "48.92", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2555.87", "11.8", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + Calendar repaymentDate = Calendar.getInstance(); + repaymentDate.add(Calendar.DAY_OF_MONTH, -7 * 2); + final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime()); + Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod"); + totalDueForCurrentPeriod = totalDueForCurrentPeriod - new Float("252.89"); + this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(); + addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "252.89"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2492.89", "36.02", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2530.13", "11.68", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + repaymentDate = Calendar.getInstance(); + repaymentDate.add(Calendar.DAY_OF_MONTH, -3); + final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime()); + totalDueForCurrentPeriod = (Float) loanSchedule.get(2).get("totalDueForPeriod"); + this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID); + + loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); + expectedvalues = new ArrayList<>(); + todaysDate = Calendar.getInstance(); + addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "252.89"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2498.39", "30.52", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, "2524.63", "11.65", "0.0", "0.0"); + verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + + } + @Test public void testLoanScheduleWithInterestRecalculation_WITH_PERIODIC_ACCOUNTING() { this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); @@ -3533,7 +3654,7 @@ public class ClientLoanIntegrationTest { } - private void addRepaymentValues(List> expectedvalues, Calendar todaysDate, int addMonths, String principalDue, + private void addRepaymentValues(List> expectedvalues, Calendar todaysDate, int addMonths, String principalDue, String interestDue, String feeChargesDue, String penaltyChargesDue) { Map values = new HashMap<>(3); values.put("dueDate", getDateAsArray(todaysDate, addMonths)); @@ -3560,6 +3681,15 @@ public class ClientLoanIntegrationTest { final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, final String recalculationRestFrequencyDate, final Account[] accounts) { + return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, + rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, + recalculationRestFrequencyDate, accounts, null); + } + + private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy, + final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, + final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, + final String recalculationRestFrequencyDate, final Account[] accounts, final String chargeId) { System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); LoanProductTestBuilder builder = new LoanProductTestBuilder() .withPrincipal("10000000.00") @@ -3578,7 +3708,7 @@ public class ClientLoanIntegrationTest { builder = builder.withAccountingRulePeriodicAccrual(accounts); } - final String loanProductJSON = builder.build(null); + final String loanProductJSON = builder.build(chargeId); return this.loanTransactionHelper.getLoanProductId(loanProductJSON); } diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/charges/ChargesHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/charges/ChargesHelper.java index 94eaede77..702341d89 100755 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/charges/ChargesHelper.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/charges/ChargesHelper.java @@ -222,6 +222,18 @@ public class ChargesHelper { System.out.println(chargesCreateJson); return chargesCreateJson; } + + public static String getLoanOverdueFeeJSONWithCalculattionTypePercentage() { + final HashMap map = populateDefaultsForLoan(); + map.put("penalty", ChargesHelper.penalty); + map.put("amount", "10"); + map.put("chargePaymentMode", ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR); + map.put("chargeTimeType", CHARGE_OVERDUE_INSTALLMENT_FEE); + map.put("chargeCalculationType", ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST); + String chargesCreateJson = new Gson().toJson(map); + System.out.println(chargesCreateJson); + return chargesCreateJson; + } public static HashMap populateDefaultsForLoan() { final HashMap map = new HashMap<>(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java index 281a4ef1a..fc3d8422c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/accounting/producttoaccountmapping/serialization/ProductToGLAccountMappingFromApiJsonDeserializer.java @@ -74,7 +74,7 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer { // accounting related data validation final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); - baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3); + baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4); if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/HolidayDetailDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/HolidayDetailDTO.java new file mode 100755 index 000000000..9d72bc10f --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/HolidayDetailDTO.java @@ -0,0 +1,52 @@ +package org.mifosplatform.portfolio.loanaccount.data; + +import java.util.List; + +import org.mifosplatform.organisation.holiday.domain.Holiday; +import org.mifosplatform.organisation.workingdays.domain.WorkingDays; + +public class HolidayDetailDTO { + + final boolean isHolidayEnabled; + final List holidays; + final WorkingDays workingDays; + final boolean allowTransactionsOnHoliday; + final boolean allowTransactionsOnNonWorkingDay; + + public HolidayDetailDTO(final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + this.isHolidayEnabled = isHolidayEnabled; + this.holidays = holidays; + this.workingDays = workingDays; + this.allowTransactionsOnHoliday = false; + this.allowTransactionsOnNonWorkingDay = false; + } + + public HolidayDetailDTO(final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, + final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay) { + this.isHolidayEnabled = isHolidayEnabled; + this.holidays = holidays; + this.workingDays = workingDays; + this.allowTransactionsOnHoliday = allowTransactionsOnHoliday; + this.allowTransactionsOnNonWorkingDay = allowTransactionsOnNonWorkingDay; + } + + public boolean isHolidayEnabled() { + return this.isHolidayEnabled; + } + + public List getHolidays() { + return this.holidays; + } + + public WorkingDays getWorkingDays() { + return this.workingDays; + } + + public boolean isAllowTransactionsOnHoliday() { + return this.allowTransactionsOnHoliday; + } + + public boolean isAllowTransactionsOnNonWorkingDay() { + return this.allowTransactionsOnNonWorkingDay; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java new file mode 100755 index 000000000..1d314607c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java @@ -0,0 +1,97 @@ +package org.mifosplatform.portfolio.loanaccount.data; + +import org.joda.time.LocalDate; +import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; + +public class ScheduleGeneratorDTO { + + final LoanScheduleGeneratorFactory loanScheduleFactory; + final ApplicationCurrency applicationCurrency; + final LocalDate calculatedRepaymentsStartingFromDate; + final HolidayDetailDTO holidayDetailDTO; + final CalendarInstance calendarInstanceForInterestRecalculation; + LocalDate recalculateFrom; + final Long overdurPenaltyWaitPeriod; + LocalDate lastTransactionDate; + + public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, + final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO) { + + this.loanScheduleFactory = loanScheduleFactory; + this.applicationCurrency = applicationCurrency; + this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; + this.calendarInstanceForInterestRecalculation = null; + this.recalculateFrom = null; + this.overdurPenaltyWaitPeriod = null; + this.lastTransactionDate = null; + this.holidayDetailDTO = holidayDetailDTO; + + } + + public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, + final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO, + final CalendarInstance calendarInstanceForInterestRecalculation, final LocalDate recalculateFrom, + final Long overdurPenaltyWaitPeriod, final LocalDate lastTransactionDate) { + + this.loanScheduleFactory = loanScheduleFactory; + this.applicationCurrency = applicationCurrency; + this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; + this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation; + this.recalculateFrom = recalculateFrom; + this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod; + this.lastTransactionDate = lastTransactionDate; + this.holidayDetailDTO = holidayDetailDTO; + + } + + public LoanScheduleGeneratorFactory getLoanScheduleFactory() { + return this.loanScheduleFactory; + } + + public ApplicationCurrency getApplicationCurrency() { + return this.applicationCurrency; + } + + public LocalDate getCalculatedRepaymentsStartingFromDate() { + return this.calculatedRepaymentsStartingFromDate; + } + + public CalendarInstance getCalendarInstanceForInterestRecalculation() { + return this.calendarInstanceForInterestRecalculation; + } + + public LocalDate getRecalculateFrom() { + return this.recalculateFrom; + } + + public Long getOverdurPenaltyWaitPeriod() { + return this.overdurPenaltyWaitPeriod; + } + + public int getPenaltyWaitPeriod() { + int penaltyWaitPeriod = 0; + if (this.overdurPenaltyWaitPeriod != null) { + penaltyWaitPeriod = this.overdurPenaltyWaitPeriod.intValue(); + } + return penaltyWaitPeriod; + } + + public LocalDate getLastTransactionDate() { + return this.lastTransactionDate; + } + + public HolidayDetailDTO getHolidayDetailDTO() { + return this.holidayDetailDTO; + } + + public void setRecalculateFrom(LocalDate recalculateFrom) { + this.recalculateFrom = recalculateFrom; + } + + public void setLastTransactionDate(LocalDate lastTransactionDate) { + this.lastTransactionDate = lastTransactionDate; + } + +} 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 c223f7bf0..e38db157c 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 @@ -75,7 +75,9 @@ import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; +import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException; import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; @@ -466,10 +468,7 @@ public class Loan extends AbstractPersistable { this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration; } - public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge, final List existingTransactionIds, - final List existingReversedTransactionIds, final List holidays, final WorkingDays workingDays, - final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, - final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { + public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge) { ChangedTransactionDetail changedTransactionDetail = null; validateLoanIsNotClosed(loanCharge); @@ -526,22 +525,14 @@ public class Loan extends AbstractPersistable { // store Id's of existing loan transactions and existing reversed loan // transactions - existingTransactionIds.addAll(findExistingTransactionIds()); - existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - - if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() - && (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) { - LocalDate recalculateFrom = null; - regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); - } else { - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); - } + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); if (!loanCharge.isDueAtDisbursement()) { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } @@ -652,12 +643,18 @@ public class Loan extends AbstractPersistable { && !loanCharge.isDueForCollectionFromAndUpToAndIncluding(disbursementDate, lastRepaymentPeriodDueDate)) { final String defaultUserMessage = "This charge with specified due date cannot be added as the it is not in schedule range."; throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage, - getDisbursementDate(), getLastRepaymentPeriodDueDate(), loanCharge.name()); + getDisbursementDate(), lastRepaymentPeriodDueDate, loanCharge.name()); } } private LocalDate getLastRepaymentPeriodDueDate() { - return this.repaymentScheduleInstallments.get(this.repaymentScheduleInstallments.size() - 1).getDueDate(); + LocalDate lastRepaymentDate = getDisbursementDate(); + for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { + if (!installment.isRecalculatedInterestComponent() && installment.getDueDate().isAfter(lastRepaymentDate)) { + lastRepaymentDate = installment.getDueDate(); + } + } + return lastRepaymentDate; } public void removeLoanCharge(final LoanCharge loanCharge) { @@ -674,7 +671,8 @@ public class Loan extends AbstractPersistable { if (removed) { charge.setActive(false); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } @@ -693,7 +691,7 @@ public class Loan extends AbstractPersistable { ***/ final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, charges()); + getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); } updateLoanSummaryDerivedFields(); @@ -751,11 +749,12 @@ public class Loan extends AbstractPersistable { ***/ final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, charges()); + getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); } else { // reprocess loan schedule based on charge been waived. final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); } updateLoanSummaryDerivedFields(); @@ -838,10 +837,7 @@ public class Loan extends AbstractPersistable { public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, final Map changes, final List existingTransactionIds, final List existingReversedTransactionIds, - final Integer loanInstallmentNumber, final List holidays, final WorkingDays workingDays, - final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, - final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation, - final Money accruedCharge) { + final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge) { validateLoanIsNotClosed(loanCharge); @@ -887,9 +883,7 @@ public class Loan extends AbstractPersistable { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() && (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) { - LocalDate recalculateFrom = null; - regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); } // Waive of charges whose due date falls after latest 'repayment' // transaction dont require entire loan schedule to be reprocessed. @@ -906,11 +900,12 @@ public class Loan extends AbstractPersistable { ***/ final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, charges()); + getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); } else { // reprocess loan schedule based on charge been waived. final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); } updateLoanSummaryDerivedFields(); @@ -1032,34 +1027,74 @@ public class Loan extends AbstractPersistable { updateLoanScheduleDependentDerivedFields(); updateLoanSummaryDerivedFields(); - resetAccruals(); + applyAccurals(); } /** - * method to reverse all the accrual entries + * method updates accrual derived fields on installments and reverse the + * unprocessed transactions */ - private void resetAccruals() { - if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return; } - List transactions = this.getLoanTransactions(); - for (final LoanTransaction transaction : transactions) { - if (transaction.isAccrual()) { - transaction.reverse(); - } + private void applyAccurals() { + Collection accruals = retreiveListOfAccrualTransactions(); + if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { + applyPeriodicAccruals(accruals); + } else if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) { + updateAccrualsForNonPeriodicAccruals(accruals); } } - public boolean isAccrualsPresent() { - boolean isAccrualsPresent = false; - if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return isAccrualsPresent; } - List transactions = this.getLoanTransactions(); - for (final LoanTransaction transaction : transactions) { - if (transaction.isAccrual() && transaction.isNotReversed()) { - isAccrualsPresent = true; - break; + private void applyPeriodicAccruals(final Collection accruals) { + + for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { + Money interest = Money.zero(getCurrency()); + Money fee = Money.zero(getCurrency()); + Money penality = Money.zero(getCurrency()); + for (LoanTransaction loanTransaction : accruals) { + if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate()) + && !loanTransaction.getTransactionDate().isAfter(installment.getDueDate())) { + interest = interest.plus(loanTransaction.getInterestPortion(getCurrency())); + fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency())); + penality = penality.plus(loanTransaction.getPenaltyChargesPortion(getCurrency())); + if (installment.getFeeChargesCharged(getCurrency()).isLessThan(fee) + || installment.getInterestCharged(getCurrency()).isLessThan(interest) + || installment.getPenaltyChargesCharged(getCurrency()).isLessThan(penality)) { + interest = interest.minus(loanTransaction.getInterestPortion(getCurrency())); + fee = fee.minus(loanTransaction.getFeeChargesPortion(getCurrency())); + penality = penality.minus(loanTransaction.getPenaltyChargesPortion(getCurrency())); + loanTransaction.reverse(); + } + } + } + installment.updateAccrualPortion(interest, fee, penality); + } + } + + private void updateAccrualsForNonPeriodicAccruals(final Collection accruals) { + + final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); + for (LoanTransaction loanTransaction : accruals) { + if (loanTransaction.getInterestPortion(getCurrency()).isGreaterThanZero()) { + if (loanTransaction.getInterestPortion(getCurrency()).isNotEqualTo(interestApplied)) { + loanTransaction.reverse(); + final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied, + getDisbursementDate()); + this.loanTransactions.add(interestAppliedTransaction); + } + } else { + Set chargePaidBies = loanTransaction.getLoanChargesPaid(); + for (final LoanChargePaidBy chargePaidBy : chargePaidBies) { + LoanCharge loanCharge = chargePaidBy.getLoanCharge(); + Money chargeAmount = loanCharge.getAmount(getCurrency()); + if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(getCurrency()))) { + loanTransaction.reverse(); + handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate()); + } + + } } } - return isAccrualsPresent; + } private void updateLoanScheduleDependentDerivedFields() { @@ -1339,8 +1374,9 @@ public class Loan extends AbstractPersistable { public void recalculateAllCharges() { Set charges = this.charges(); + int penaltyWaitPeriod = 0; for (final LoanCharge loanCharge : charges) { - recalculateLoanCharge(loanCharge); + recalculateLoanCharge(loanCharge, penaltyWaitPeriod); } } @@ -1375,8 +1411,25 @@ public class Loan extends AbstractPersistable { } } - private void recalculateLoanCharge(final LoanCharge loanCharge) { - final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge); + private void updateOverdueScheduleInstallment(final LoanCharge loanCharge) { + if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isActive()) { + LoanOverdueInstallmentCharge overdueInstallmentCharge = loanCharge.getOverdueInstallmentCharge(); + if (overdueInstallmentCharge != null) { + Integer installmentNumber = overdueInstallmentCharge.getInstallment().getInstallmentNumber(); + LoanRepaymentScheduleInstallment installment = fetchRepaymentScheduleInstallment(installmentNumber); + overdueInstallmentCharge.updateLoanRepaymentScheduleInstallment(installment); + } + } + } + + private void recalculateLoanCharge(final LoanCharge loanCharge, final int penaltyWaitPeriod) { + BigDecimal amount = BigDecimal.ZERO; + if (loanCharge.isOverdueInstallmentCharge()) { + amount = calculateOverdueAmountPercentageAppliedTo(loanCharge, penaltyWaitPeriod); + } else { + amount = calculateAmountPercentageAppliedTo(loanCharge); + } + BigDecimal chargeAmt = BigDecimal.ZERO; BigDecimal totalChargeAmt = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageBased()) { @@ -1390,9 +1443,46 @@ public class Loan extends AbstractPersistable { chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments())); } } - loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(), - totalChargeAmt); - validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); + if (loanCharge.isActive()) { + loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(), + totalChargeAmt); + validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); + } + + } + + private BigDecimal calculateOverdueAmountPercentageAppliedTo(final LoanCharge loanCharge, final int penaltyWaitPeriod) { + LoanRepaymentScheduleInstallment installment = loanCharge.getOverdueInstallmentCharge().getInstallment(); + LocalDate graceDate = LocalDate.now().minusDays(penaltyWaitPeriod); + Money amount = Money.zero(getCurrency()); + if (graceDate.isAfter(installment.getDueDate())) { + amount = calculateOverdueAmountPercentageAppliedTo(installment, loanCharge.getChargeCalculation()); + if (!amount.isGreaterThanZero()) { + loanCharge.setActive(false); + } + } else { + loanCharge.setActive(false); + } + return amount.getAmount(); + } + + private Money calculateOverdueAmountPercentageAppliedTo(LoanRepaymentScheduleInstallment installment, + ChargeCalculationType calculationType) { + Money amount = Money.zero(getCurrency()); + switch (calculationType) { + case PERCENT_OF_AMOUNT: + amount = installment.getPrincipalOutstanding(getCurrency()); + break; + case PERCENT_OF_AMOUNT_AND_INTEREST: + amount = installment.getPrincipalOutstanding(getCurrency()).plus(installment.getInterestOutstanding(getCurrency())); + break; + case PERCENT_OF_INTEREST: + amount = installment.getInterestOutstanding(getCurrency()); + break; + default: + break; + } + return amount; } private void updateDisbursementDetails(final JsonCommand command, final Map actualChanges) { @@ -1588,8 +1678,9 @@ public class Loan extends AbstractPersistable { } // charges are optional + int penaltyWaitPeriod = 0; for (final LoanCharge loanCharge : charges()) { - recalculateLoanCharge(loanCharge); + recalculateLoanCharge(loanCharge, penaltyWaitPeriod); } updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); @@ -1815,11 +1906,8 @@ public class Loan extends AbstractPersistable { return ids; } - public ChangedTransactionDetail disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser, - final JsonCommand command, final ApplicationCurrency currency, final Map actualChanges, - final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, - final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay, - final CalendarInstance calendarInstanceForInterestRecalculation) { + public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map actualChanges, + final ScheduleGeneratorDTO scheduleGeneratorDTO) { final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); @@ -1836,15 +1924,15 @@ public class Loan extends AbstractPersistable { actualChanges.put("dateFormat", command.dateFormat()); actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate")); + HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); + // validate if disbursement date is a holiday or a non-working day - validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay); - validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); + validateDisbursementDateIsOnNonWorkingDay(holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); + validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays()); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() && (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) { - LocalDate recalculateFrom = null; - regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); } updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); @@ -1870,9 +1958,7 @@ public class Loan extends AbstractPersistable { } - public void regenerateScheduleOnDisbursement(final LoanScheduleGeneratorFactory loanScheduleFactory, - final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays, final boolean recalculateSchedule, + public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule, final LocalDate actualDisbursementDate, BigDecimal emiAmount) { boolean isEmiAmountChanged = false; if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) { @@ -1883,8 +1969,7 @@ public class Loan extends AbstractPersistable { } if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) { - regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, - workingDays); + regenerateRepaymentSchedule(scheduleGeneratorDTO); } } @@ -1958,7 +2043,8 @@ public class Loan extends AbstractPersistable { .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } @@ -2068,25 +2154,20 @@ public class Loan extends AbstractPersistable { * Ability to regenerate the repayment schedule based on the loans current * details/state. */ - private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory, - final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, - final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + private void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO) { - final LoanScheduleModel loanSchedule = regenerateScheduleModel(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + final LoanScheduleModel loanSchedule = regenerateScheduleModel(scheduleGeneratorDTO); updateLoanSchedule(loanSchedule); Set charges = this.charges(); for (LoanCharge loanCharge : charges) { - recalculateLoanCharge(loanCharge); + recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod()); } } - public LoanScheduleModel regenerateScheduleModel(final LoanScheduleGeneratorFactory loanScheduleFactory, - final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, - final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO scheduleGeneratorDTO) { final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); - final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); + final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod); final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final MathContext mc = new MathContext(8, roundingMode); @@ -2113,14 +2194,14 @@ public class Loan extends AbstractPersistable { loanVariationTermsData.add(data); } - final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, - loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), - calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(), + loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), + scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate()); - final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, charges(), - isHolidayEnabled, holidays, workingDays); + final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(), + scheduleGeneratorDTO.getHolidayDetailDTO()); return loanSchedule; } @@ -2220,10 +2301,8 @@ public class Loan extends AbstractPersistable { return chargesPayment; } - public Map undoDisbursal(final LoanScheduleGeneratorFactory loanScheduleFactory, - final ApplicationCurrency applicationCurrency, final List existingTransactionIds, - final List existingReversedTransactionIds, final LocalDate calculatedRepaymentsStartingFromDate, - final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + public Map undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List existingTransactionIds, + final List existingReversedTransactionIds) { validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO); @@ -2254,8 +2333,7 @@ public class Loan extends AbstractPersistable { // clear off actual disbusrement date so schedule regeneration // uses expected date. - regenerateRepaymentSchedule(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays); + regenerateRepaymentSchedule(scheduleGeneratorDTO); if (isDisbueseAmtChanged) { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } @@ -2298,38 +2376,35 @@ public class Loan extends AbstractPersistable { this.loanTermVariations.clear(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); updateLoanSummaryDerivedFields(); } public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, - final List existingReversedTransactionIds, final List holidays, final WorkingDays workingDays, - final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, - final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { + final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, waiveInterestTransaction.getTransactionDate()); + validateActivityNotBeforeLastTransactionDateForInterestRecalculation(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, + waiveInterestTransaction.getTransactionDate()); existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction, - loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, - holidays, workingDays, calendarInstanceForInterestRecalculation); + loanLifecycleStateMachine, null, scheduleGeneratorDTO); return changedTransactionDetail; } 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, final boolean isHolidayEnabled, - boolean isRecoveryRepayment, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, - final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { + final List existingReversedTransactionIds, boolean isRecoveryRepayment, final ScheduleGeneratorDTO scheduleGeneratorDTO) { LoanEvent event = null; if (isRecoveryRepayment) { @@ -2338,31 +2413,36 @@ public class Loan extends AbstractPersistable { event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER; } + HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); validateAccountStatus(event); validateActivityNotBeforeClientOrGroupTransferDate(event, repaymentTransaction.getTransactionDate()); - - validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays); - validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); + validateActivityNotBeforeLastTransactionDateForInterestRecalculation(event, repaymentTransaction.getTransactionDate()); + validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(), + holidayDetailDTO.getHolidays()); + validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(), + holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, - loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, - holidays, workingDays, calendarInstanceForInterestRecalculation); + loanLifecycleStateMachine, null, scheduleGeneratorDTO); return changedTransactionDetail; } public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, final List existingReversedTransactionIds, - final boolean allowTransactionsOnHoliday, final List holidays, final WorkingDays workingDays, - final boolean allowTransactionsOnNonWorkingDay, final LoanTransaction paymentTransaction, final Integer installmentNumber) { + final HolidayDetailDTO holidayDetailDTO, final LoanTransaction paymentTransaction, final Integer installmentNumber) { validateAccountStatus(LoanEvent.LOAN_CHARGE_PAYMENT); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate()); - validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays); - validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); + validateActivityNotBeforeLastTransactionDateForInterestRecalculation(LoanEvent.LOAN_CHARGE_PAYMENT, + paymentTransaction.getTransactionDate()); + validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(), + holidayDetailDTO.getHolidays()); + validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(), + holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); if (paymentTransaction.getTransactionDate().isAfter(new LocalDate())) { final String errorMessage = "The date on which a loan charge paid cannot be in the future."; @@ -2415,9 +2495,7 @@ public class Loan extends AbstractPersistable { private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction, 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) { + final ScheduleGeneratorDTO scheduleGeneratorDTO) { ChangedTransactionDetail changedTransactionDetail = null; @@ -2516,12 +2594,12 @@ public class Loan extends AbstractPersistable { } if (reprocess) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } @@ -2607,6 +2685,18 @@ public class Loan extends AbstractPersistable { return repaymentsOrWaivers; } + private List retreiveListOfAccrualTransactions() { + final List transactions = new ArrayList<>(); + for (final LoanTransaction transaction : this.loanTransactions) { + if (transaction.isNotReversed() && transaction.isAccrual()) { + transactions.add(transaction); + } + } + final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); + Collections.sort(transactions, transactionComparator); + return transactions; + } + private void doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { if (isOverPaid()) { @@ -2767,13 +2857,13 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, final List existingTransactionIds, final List existingReversedTransactionIds, - final boolean allowTransactionsOnHoliday, final List holidays, final WorkingDays workingDays, - final boolean isHolidayEnabled, final boolean allowTransactionsOnNonWorkingDay, - final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, - final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { + final ScheduleGeneratorDTO scheduleGeneratorDTO) { - validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), allowTransactionsOnHoliday, holidays); - validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); + HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); + validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(), + holidayDetailDTO.getHolidays()); + validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(), + holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); ChangedTransactionDetail changedTransactionDetail = null; @@ -2803,17 +2893,14 @@ public class Loan extends AbstractPersistable { if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) { changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, - transactionForAdjustment, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, - holidays, workingDays, calendarInstanceForInterestRecalculation); + transactionForAdjustment, scheduleGeneratorDTO); } return changedTransactionDetail; } 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) { + final List existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) { validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); existingTransactionIds.addAll(findExistingTransactionIds()); @@ -2825,13 +2912,11 @@ public class Loan extends AbstractPersistable { .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - LocalDate recalculateFrom = null; - regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); } ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, - charges()); + charges(), getLastUserTransactionForChargeCalc()); updateLoanSummaryDerivedFields(); return changedTransactionDetail; } @@ -2893,15 +2978,12 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, 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 CalendarInstance calendarInstanceForInterestRecalculation) { + final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, - calendarInstanceForInterestRecalculation); + ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, + loanRepaymentScheduleTransactionProcessor); validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING); @@ -2960,25 +3042,20 @@ public class Loan extends AbstractPersistable { return changedTransactionDetail; } - 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 CalendarInstance calendarInstanceForInterestRecalculation) { + private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO scheduleGeneratorDTO, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { ChangedTransactionDetail changedTransactionDetail = null; if (isDisbursementAllowed() && atleastOnceDisbursed()) { this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount()); removeDisbursementDetail(); - regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, - workingDays); + regenerateRepaymentSchedule(scheduleGeneratorDTO); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - LocalDate recalculateFrom = null; - regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), + getLastUserTransactionForChargeCalc()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); this.loanTransactions.add(mapEntry.getValue()); @@ -3003,9 +3080,7 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, 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 CalendarInstance calendarInstanceForInterestRecalculation) { + final ScheduleGeneratorDTO scheduleGeneratorDTO) { validateAccountStatus(LoanEvent.LOAN_CLOSED); @@ -3032,9 +3107,8 @@ public class Loan extends AbstractPersistable { } final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, - calendarInstanceForInterestRecalculation); + ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO, + loanRepaymentScheduleTransactionProcessor); LoanTransaction loanTransaction = null; if (isOpen()) { @@ -3596,7 +3670,8 @@ public class Loan extends AbstractPersistable { final List> newLoanTransactions = new ArrayList<>(); for (final LoanTransaction transaction : this.loanTransactions) { - if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) { + if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId()) + && !existingReversedTransactionIds.contains(transaction.getId())) { newLoanTransactions.add(transaction.toMapData(currencyData)); } else if (!existingTransactionIds.contains(transaction.getId())) { newLoanTransactions.add(transaction.toMapData(currencyData)); @@ -3621,10 +3696,13 @@ public class Loan extends AbstractPersistable { if (receivableInterest.isLessThanZero()) { receivableInterest = receivableInterest.zero(); } - /* if (transaction.getTransactionDate().isAfter(tillDate) && transaction.isAccrual()) { - final String errorMessage = "The date on which a loan is interest waived cannot be in after accrual transactions."; - throw new InvalidLoanStateTransitionException("waive", "cannot.be.after.accrual.date", errorMessage, tillDate); - }*/ + /* + * if (transaction.getTransactionDate().isAfter(tillDate) && + * transaction.isAccrual()) { final String errorMessage = + * "The date on which a loan is interest waived cannot be in after accrual transactions." + * ; throw new InvalidLoanStateTransitionException("waive", + * "cannot.be.after.accrual.date", errorMessage, tillDate); } + */ } return receivableInterest; } @@ -3902,6 +3980,57 @@ public class Loan extends AbstractPersistable { } } + private void validateActivityNotBeforeLastTransactionDateForInterestRecalculation(final LoanEvent event, final LocalDate activityDate) { + if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return; } + LocalDate lastTransactionDate = getLastUserTransactionDate(); + final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate(); + if (lastTransactionDate.isAfter(activityDate)) { + String errorMessage = null; + String action = null; + String postfix = null; + switch (event) { + case LOAN_REPAYMENT_OR_WAIVER: + errorMessage = "The date on which a repayment or waiver is made cannot be earlier than last transaction date"; + action = "repayment.or.waiver"; + postfix = "cannot.be.made.before.last.transaction.date"; + break; + case WRITE_OFF_OUTSTANDING: + errorMessage = "The date on which a write off is made cannot be earlier than last transaction date"; + action = "writeoff"; + postfix = "cannot.be.made.before.last.transaction.date"; + break; + case LOAN_CHARGE_PAYMENT: + errorMessage = "The date on which a charge payment is made cannot be earlier than last transaction date"; + action = "charge.payment"; + postfix = "cannot.be.made.before.last.transaction.date"; + break; + default: + break; + } + throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, clientOfficeJoiningDate); + } + } + + private LocalDate getLastUserTransactionDate() { + LocalDate currentTransactionDate = getDisbursementDate(); + for (final LoanTransaction previousTransaction : this.loanTransactions) { + if (!(previousTransaction.isReversed() || previousTransaction.isAccrual())) { + if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) { + currentTransactionDate = previousTransaction.getTransactionDate(); + } + } + } + return currentTransactionDate; + } + + public LocalDate getLastUserTransactionForChargeCalc() { + LocalDate lastTransaction = getDisbursementDate(); + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + lastTransaction = getLastUserTransactionDate(); + } + return lastTransaction; + } + public Set charges() { Set loanCharges = new HashSet<>(); if (this.charges != null) { @@ -4081,9 +4210,7 @@ public class Loan extends AbstractPersistable { public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails, 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 CalendarInstance calendarInstanceForInterestRecalculation) { + final Map actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO) { validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName); disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate()); @@ -4095,12 +4222,9 @@ public class Loan extends AbstractPersistable { existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - LocalDate recalculateFrom = null; - regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom); + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); } else { - regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, - workingDays); + regenerateRepaymentSchedule(scheduleGeneratorDTO); } final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory @@ -4108,7 +4232,7 @@ public class Loan extends AbstractPersistable { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, - charges()); + charges(), getLastUserTransactionForChargeCalc()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); this.loanTransactions.add(mapEntry.getValue()); @@ -4178,31 +4302,40 @@ public class Loan extends AbstractPersistable { return maturityDate; } - public ChangedTransactionDetail recalculateScheduleFromLastTransaction(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) { + public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final ScheduleGeneratorDTO generatorDTO, + final List existingTransactionIds, final List existingReversedTransactionIds) { existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); - LocalDate recalculateFrom = null; - List loanTransactions = this.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); - for (LoanTransaction loanTransaction : loanTransactions) { - if (recalculateFrom == null || loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { - recalculateFrom = loanTransaction.getTransactionDate(); - } - } + /* + * LocalDate recalculateFrom = null; List + * loanTransactions = + * this.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); for + * (LoanTransaction loanTransaction : loanTransactions) { if + * (recalculateFrom == null || + * loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { + * recalculateFrom = loanTransaction.getTransactionDate(); } } + * generatorDTO.setRecalculateFrom(recalculateFrom); + */ if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, - calendarInstanceForInterestRecalculation, recalculateFrom); + regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO); } + return processTransactions(); + + } + + public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) { + regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO); + return processTransactions(); + + } + + private ChangedTransactionDetail processTransactions() { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, - charges()); + charges(), getLastUserTransactionForChargeCalc()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } @@ -4216,28 +4349,38 @@ public class Loan extends AbstractPersistable { updateLoanSummaryDerivedFields(); this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values()); - return changedTransactionDetail; + 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, LocalDate recalculateFrom) { + private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) { - final LoanScheduleModel loanSchedule = getRecalculatedSchedule(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, - recalculateFrom); + LocalDate lastTransactionDate = getLastUserTransactionDate(); + generatorDTO.setLastTransactionDate(lastTransactionDate); + final LoanScheduleModel loanSchedule = getRecalculatedSchedule(generatorDTO); if (loanSchedule == null) { return; } - updateLoanSchedule(loanSchedule); LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(); + + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory + .determineProcessor(this.transactionProcessingStrategy); + final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); + final List copyTransactions = new ArrayList<>(); + for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) { + copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); + } + loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(getDisbursementDate(), copyTransactions, + getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); + Set charges = this.charges(); for (LoanCharge loanCharge : charges) { if (!loanCharge.isDueAtDisbursement()) { - if (loanCharge.getDueLocalDate() == null || !lastRepaymentDate.isBefore(loanCharge.getDueLocalDate())) { - recalculateLoanCharge(loanCharge); - loanCharge.updateWaivedAmount(getCurrency()); + updateOverdueScheduleInstallment(loanCharge); + if (loanCharge.getDueLocalDate() == null || (!lastRepaymentDate.isBefore(loanCharge.getDueLocalDate()))) { + if (!loanCharge.isWaived() && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { + recalculateLoanCharge(loanCharge, generatorDTO.getPenaltyWaitPeriod()); + loanCharge.updateWaivedAmount(getCurrency()); + } } else { loanCharge.setActive(false); } @@ -4246,28 +4389,25 @@ public class Loan extends AbstractPersistable { updateLoanSummaryDerivedFields(); } - private LoanScheduleModel getRecalculatedSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory, - final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, - final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, - final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) { + private LoanScheduleModel getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) { - if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return null; } + if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || !isOpen()) { return null; } final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); - final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); + final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod); final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final MathContext mc = new MathContext(8, roundingMode); - final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(applicationCurrency, - calculatedRepaymentsStartingFromDate, calendarInstanceForInterestRecalculation); + final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO.getApplicationCurrency(), + generatorDTO.getCalculatedRepaymentsStartingFromDate(), generatorDTO.getCalendarInstanceForInterestRecalculation()); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); - final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency, - loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays, - retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor, - this.repaymentScheduleInstallments, recalculateFrom); + final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), + generatorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(), + loanRepaymentScheduleTransactionProcessor, this.repaymentScheduleInstallments, generatorDTO.getRecalculateFrom(), + generatorDTO.getLastTransactionDate(), generatorDTO.getPenaltyWaitPeriod()); return loanSchedule; } @@ -4576,4 +4716,12 @@ public class Loan extends AbstractPersistable { this.termFrequency = termFrequency; } } + + public boolean isFeeCompoundingEnabledForInterestRecalculation() { + boolean isEnabled = false; + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + isEnabled = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled(); + } + return isEnabled; + } } \ No newline at end of file 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 54cb84eeb..8784bd049 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 @@ -20,6 +20,7 @@ import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomain import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.holiday.domain.HolidayRepository; @@ -43,7 +44,9 @@ import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData; +import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanaccount.service.LoanAccrualWritePlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; @@ -149,6 +152,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { ApplicationCurrency applicationCurrency = null; LocalDate calculatedRepaymentsStartingFromDate = null; boolean isHolidayEnabled = false; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate recalculateDueDateChargesFrom = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); @@ -161,12 +167,17 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { calendarInstance); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, + allowTransactionsOnNonWorkingDay); + final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + recalculateDueDateChargesFrom); final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, - defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday, - holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory, - applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); + defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment, + scheduleGeneratorDTO); saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction); @@ -259,17 +270,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail, transactionDate, txnExternalId, loanTransactionType); - final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), - transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue()); - final WorkingDays workingDays = this.workingDaysRepository.findOne(); - final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); + if (loanTransactionType.isRepaymentAtDisbursement()) { loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds); } else { + final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled(); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), + transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue()); + final WorkingDays workingDays = this.workingDaysRepository.findOne(); + final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); + final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, + allowTransactionsOnNonWorkingDay); + loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, - allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction, - installmentNumber); + holidayDetailDTO, newPaymentTransaction, installmentNumber); } saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction); saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -422,7 +437,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { @Override public void recalculateAccruals(Loan loan) { LocalDate accruedTill = loan.getAccruedTill(); - if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || accruedTill == null || loan.isAccrualsPresent()) { return; } + if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !loan.repaymentScheduleDetail().isInterestRecalculationEnabled() + || accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; } Collection loanScheduleAccrualDatas = new ArrayList<>(); List installments = loan.fetchRepaymentScheduleInstallments(); Long loanId = loan.getId(); @@ -436,9 +452,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); CurrencyData currencyData = applicationCurrency.toData(); Set loanCharges = loan.charges(); - BigDecimal accruedInterestIncome = null; - BigDecimal accruedFeeIncome = null; - BigDecimal accruedPenaltyIncome = null; for (LoanRepaymentScheduleInstallment installment : installments) { if (!accruedTill.isBefore(installment.getDueDate()) @@ -463,7 +476,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(), installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(), installment .getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(), - accruedInterestIncome, accruedFeeIncome, accruedPenaltyIncome, currencyData, interestCalculatedFrom, installment + installment.getInterestAccrued(currency).getAmount(), installment.getFeeAccrued(currency).getAmount(), installment + .getPenaltyAccrued(currency).getAmount(), currencyData, interestCalculatedFrom, installment .getInterestWaived(currency).getAmount()); loanScheduleAccrualDatas.add(accrualData); @@ -471,7 +485,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } if (!loanScheduleAccrualDatas.isEmpty()) { - this.accrualWritePlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualDatas); + String error = this.accrualWritePlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualDatas); + if (error.length() > 0) { + String globalisationMessageCode = "error.msg.accrual.exception"; + throw new GeneralPlatformDomainRuleException(globalisationMessageCode, error, error); + } } } 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 7b4a8c99a..a1f38f63c 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 @@ -879,4 +879,8 @@ public class LoanCharge extends AbstractPersistable { } } + + public LoanOverdueInstallmentCharge getOverdueInstallmentCharge() { + return this.overdueInstallmentCharge; + } } \ 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 b69da3a4f..23df5b6ea 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 @@ -35,6 +35,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; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanOverdueInstallmentCharge.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanOverdueInstallmentCharge.java index ce95a06ce..efe584bf6 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanOverdueInstallmentCharge.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanOverdueInstallmentCharge.java @@ -24,19 +24,27 @@ public class LoanOverdueInstallmentCharge extends AbstractPersistable { @ManyToOne(optional = false) @JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false) private LoanRepaymentScheduleInstallment installment; - - @Column(name="frequency_number") + + @Column(name = "frequency_number") private Integer frequencyNumber; - - public LoanOverdueInstallmentCharge(){ - + public LoanOverdueInstallmentCharge() { + } - - public LoanOverdueInstallmentCharge(final LoanCharge loanCharge,final LoanRepaymentScheduleInstallment installment, final Integer frequencyNumber){ + + public LoanOverdueInstallmentCharge(final LoanCharge loanCharge, final LoanRepaymentScheduleInstallment installment, + final Integer frequencyNumber) { this.loancharge = loanCharge; this.installment = installment; this.frequencyNumber = frequencyNumber; } + public void updateLoanRepaymentScheduleInstallment(LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) { + this.installment = loanRepaymentScheduleInstallment; + } + + public LoanRepaymentScheduleInstallment getInstallment() { + return this.installment; + } + } \ No newline at end of file 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 6858f8367..f96e4e9b4 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 @@ -65,7 +65,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus @Column(name = "accrual_interest_derived", scale = 6, precision = 19, nullable = true) private BigDecimal interestAccrued; - + @Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true) private BigDecimal feeChargesCharged; @@ -78,6 +78,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus @Column(name = "fee_charges_waived_derived", scale = 6, precision = 19, nullable = true) private BigDecimal feeChargesWaived; + @Column(name = "accrual_fee_charges_derived", scale = 6, precision = 19, nullable = true) + private BigDecimal feeAccrued; + @Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true) private BigDecimal penaltyCharges; @@ -90,6 +93,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus @Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19, nullable = true) private BigDecimal penaltyChargesWaived; + @Column(name = "accrual_penalty_charges_derived", scale = 6, precision = 19, nullable = true) + private BigDecimal penaltyAccrued; + @Column(name = "total_paid_in_advance_derived", scale = 6, precision = 19, nullable = true) private BigDecimal totalPaidInAdvance; @@ -127,10 +133,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus this.obligationsMet = false; this.recalculatedInterestComponent = recalculatedInterestComponent; } - + public LoanRepaymentScheduleInstallment(final Loan loan) { - this.loan = loan; - this.installmentNumber = null; + this.loan = loan; + this.installmentNumber = null; this.fromDate = null; this.dueDate = null; this.obligationsMet = false; @@ -204,6 +210,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus return getInterestCharged(currency).minus(interestAccountedFor); } + public Money getInterestAccrued(final MonetaryCurrency currency) { + return Money.of(currency, this.interestAccrued); + } + public Money getFeeChargesCharged(final MonetaryCurrency currency) { return Money.of(currency, this.feeChargesCharged); } @@ -226,6 +236,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus return getFeeChargesCharged(currency).minus(feeChargesAccountedFor); } + public Money getFeeAccrued(final MonetaryCurrency currency) { + return Money.of(currency, this.feeAccrued); + } + public Money getPenaltyChargesCharged(final MonetaryCurrency currency) { return Money.of(currency, this.penaltyCharges); } @@ -248,6 +262,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus return getPenaltyChargesCharged(currency).minus(feeChargesAccountedFor); } + public Money getPenaltyAccrued(final MonetaryCurrency currency) { + return Money.of(currency, this.penaltyAccrued); + } + public boolean isInterestDue(final MonetaryCurrency currency) { return getInterestOutstanding(currency).isGreaterThanZero(); } @@ -264,10 +282,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus public void updateLoan(final Loan loan) { this.loan = loan; } - + public boolean isPartlyPaid() { - return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null - || this.principalCompleted != null); + return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null || this.principalCompleted != null); } public boolean isObligationsMet() { @@ -507,6 +524,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount()); } + public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) { + this.interestAccrued = defaultToNullIfZero(interest.getAmount()); + this.feeAccrued = defaultToNullIfZero(feeCharges.getAmount()); + this.penaltyAccrued = defaultToNullIfZero(penalityCharges.getAmount()); + } + public void updateDerivedFields(final MonetaryCurrency currency, final LocalDate actualDisbursementDate) { if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) { this.obligationsMet = true; @@ -569,59 +592,57 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus public void setRecalculatedInterestComponent(boolean recalculatedInterestComponent) { this.recalculatedInterestComponent = recalculatedInterestComponent; } - - public void updateInstallmentNumber(final Integer installmentNumber) { - if(installmentNumber != null) { - this.installmentNumber = installmentNumber; - } - } - - public void updateInterestCharged(final BigDecimal interestCharged) { - this.interestCharged = interestCharged; - } - - public void updateObligationMet(final Boolean obligationMet) { - this.obligationsMet = obligationMet; - } - - public void updateObligationMetOnDate(final LocalDate obligationsMetOnDate) { - this.obligationsMetOnDate = (obligationsMetOnDate != null) ? obligationsMetOnDate.toDate() : null; - } - - public void updateInterestWrittenOff(final BigDecimal interestWrittenOff) { - this.interestWrittenOff = interestWrittenOff; - } - - public void updatePrincipal(final BigDecimal principal) { - this.principal = principal; - } - - public static Comparator installmentNumberComparator - = new Comparator() { - @Override - public int compare(LoanRepaymentScheduleInstallment arg0, - LoanRepaymentScheduleInstallment arg1) { - - return arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber()); - } + public void updateInstallmentNumber(final Integer installmentNumber) { + if (installmentNumber != null) { + this.installmentNumber = installmentNumber; + } + } + + public void updateInterestCharged(final BigDecimal interestCharged) { + this.interestCharged = interestCharged; + } + + public void updateObligationMet(final Boolean obligationMet) { + this.obligationsMet = obligationMet; + } + + public void updateObligationMetOnDate(final LocalDate obligationsMetOnDate) { + this.obligationsMetOnDate = (obligationsMetOnDate != null) ? obligationsMetOnDate.toDate() : null; + } + + public void updateInterestWrittenOff(final BigDecimal interestWrittenOff) { + this.interestWrittenOff = interestWrittenOff; + } + + public void updatePrincipal(final BigDecimal principal) { + this.principal = principal; + } + + public static Comparator installmentNumberComparator = new Comparator() { + + @Override + public int compare(LoanRepaymentScheduleInstallment arg0, LoanRepaymentScheduleInstallment arg1) { + + return arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber()); + } }; - + public BigDecimal getTotalPaidInAdvance() { - return this.totalPaidInAdvance; + return this.totalPaidInAdvance; } - + public BigDecimal getTotalPaidLate() { - return this.totalPaidLate; + return this.totalPaidLate; } - + public LocalDate getObligationsMetOnDate() { - LocalDate obligationsMetOnDate = null; - - if(this.obligationsMetOnDate != null) { - obligationsMetOnDate = new LocalDate(this.obligationsMetOnDate); - } - - return obligationsMetOnDate; + LocalDate obligationsMetOnDate = null; + + if (this.obligationsMetOnDate != null) { + obligationsMetOnDate = new LocalDate(this.obligationsMetOnDate); + } + + return obligationsMetOnDate; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java index bae71497b..bc19378b5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java @@ -20,24 +20,29 @@ import org.mifosplatform.organisation.monetary.domain.Money; public class LoanRepaymentScheduleProcessingWrapper { public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, - final List repaymentPeriods, final Set loanCharges) { + final List repaymentPeriods, final Set loanCharges, + final LocalDate lastTransactionDate) { Money totalInterest = Money.zero(currency); + Money totalPrincipal = Money.zero(currency); for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) { totalInterest = totalInterest.plus(installment.getInterestCharged(currency)); + totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency)); } LocalDate startDate = disbursementDate; for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, - currency, period, repaymentPeriods.size(), totalInterest, !period.isRecalculatedInterestComponent()); + currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(), + lastTransactionDate); final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent()); final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent()); final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), - loanCharges, currency, period, repaymentPeriods.size(), totalInterest, !period.isRecalculatedInterestComponent()); + loanCharges, currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, + !period.isRecalculatedInterestComponent(), lastTransactionDate); final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent()); final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, @@ -53,7 +58,8 @@ public class LoanRepaymentScheduleProcessingWrapper { private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, int numberOfRepayments, - final Money totalInterest, boolean isInstallmentChargeApplicable) { + final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, + final LocalDate lastTransactionDate) { Money cumulative = Money.zero(monetaryCurrency); @@ -80,14 +86,15 @@ public class LoanRepaymentScheduleProcessingWrapper { && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) - && loanCharge.getChargeCalculation().isPercentageBased()) { + && loanCharge.getChargeCalculation().isPercentageBased() + && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(period.getLoan().getPrincpal().getAmount()).add(totalInterest.getAmount()); + amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { amount = amount.add(totalInterest.getAmount()); } else { - amount = amount.add(period.getLoan().getPrincpal().getAmount()); + amount = amount.add(totalPrincipal.getAmount()); } BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); cumulative = cumulative.plus(loanChargeAmt); @@ -144,7 +151,8 @@ public class LoanRepaymentScheduleProcessingWrapper { private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, - int numberOfRepayments, Money totalInterest, boolean isInstallmentChargeApplicable) { + int numberOfRepayments, final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, + final LocalDate lastTransactionDate) { Money cumulative = Money.zero(currency); @@ -171,14 +179,15 @@ public class LoanRepaymentScheduleProcessingWrapper { && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) - && loanCharge.getChargeCalculation().isPercentageBased()) { + && loanCharge.getChargeCalculation().isPercentageBased() + && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { - amount = amount.add(period.getLoan().getPrincpal().getAmount()).add(totalInterest.getAmount()); + amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { amount = amount.add(totalInterest.getAmount()); } else { - amount = amount.add(period.getLoan().getPrincpal().getAmount()); + amount = amount.add(totalPrincipal.getAmount()); } BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); cumulative = cumulative.plus(loanChargeAmt); 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 02c16abe4..0b5d2bbc0 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 @@ -241,7 +241,7 @@ public final class LoanTransaction extends AbstractPersistable { final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge) { final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate, null); - waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived,unrecognizedCharge); + waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge); return waiver; } @@ -304,7 +304,7 @@ public final class LoanTransaction extends AbstractPersistable { updateChargesComponents(feeCharges, penaltyCharges); } - private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) { + public void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) { final MonetaryCurrency currency = feeCharges.getCurrency(); this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount()); this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount()); @@ -322,7 +322,7 @@ public final class LoanTransaction extends AbstractPersistable { this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount()); this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedInterest).getAmount()); } - + public void adjustInterestComponent(final MonetaryCurrency currency) { this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).minus(getUnrecognizedIncomePortion(currency)).getAmount()); } @@ -503,7 +503,8 @@ public final class LoanTransaction extends AbstractPersistable { } return new LoanTransactionData(getId(), this.office.getId(), this.office.getName(), transactionType, paymentDetailData, currencyData, getTransactionDate(), this.amount, this.principalPortion, this.interestPortion, this.feeChargesPortion, - this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer, null, outstandingLoanBalance, unrecognizedIncomePortion); + this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer, null, outstandingLoanBalance, + unrecognizedIncomePortion); } public Map toMapData(final CurrencyData currencyData) { 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 780a9f566..9a90a6743 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 @@ -48,7 +48,16 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen @Override public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate, final List transactionsPostDisbursement, final MonetaryCurrency currency, - final List installments, final Set charges) { + final List installments, final Set charges, final LocalDate recalculateChargesFrom) { + final boolean reprocessCharges = true; + return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom, + reprocessCharges); + } + + private ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate, + final List transactionsPostDisbursement, final MonetaryCurrency currency, + final List installments, final Set charges, + final LocalDate recalculateChargesFrom, boolean reprocessCharges) { if (charges != null) { for (final LoanCharge loanCharge : charges) { @@ -65,8 +74,10 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen // re-process loan charges over repayment periods (picking up on waived // loan charges) - final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(currency, disbursementDate, installments, charges); + if (reprocessCharges) { + final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); + wrapper.reprocess(currency, disbursementDate, installments, charges, recalculateChargesFrom); + } final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail(); final List transactionstoBeProcessed = new ArrayList<>(); @@ -199,7 +210,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen final List installments, final Set charges, final Money chargeAmountToProcess, final boolean isFeeCharge) { // to. - if(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()){ + if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { loanTransaction.resetDerivedComponents(); } Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, chargeAmountToProcess); @@ -500,4 +511,13 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen } } + @Override + public ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(final LocalDate disbursementDate, + final List transactionsPostDisbursement, final MonetaryCurrency currency, + final List installments, final Set charges, final LocalDate recalculateChargesFrom) { + final boolean reprocessCharges = false; + return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom, + reprocessCharges); + } + } \ 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 f91f20757..234295414 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 @@ -23,7 +23,8 @@ public interface LoanRepaymentScheduleTransactionProcessor { Set charges); ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List repaymentsOrWaivers, - MonetaryCurrency currency, List repaymentScheduleInstallments, Set charges); + MonetaryCurrency currency, List repaymentScheduleInstallments, Set charges, + LocalDate recalculateChargesFrom); void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, List repaymentScheduleInstallments); @@ -31,13 +32,18 @@ public interface LoanRepaymentScheduleTransactionProcessor { List handleRepaymentSchedule(List transactionsPostDisbursement, MonetaryCurrency currency, List installments, LoanRepaymentScheduleInstallment currentInstallment, Map recalculationDates, LoanTransaction preCloseTransaction); - + /** - * Used in interest recalculation to introduce new interest only installment. + * Used in interest recalculation to introduce new interest only + * installment. */ boolean isInterestFirstRepaymentScheduleTransactionProcessor(); - + void applyTransaction(List transactionsPostDisbursement, MonetaryCurrency currency, List installments); + ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(LocalDate disbursementDate, + List transactionsPostDisbursement, MonetaryCurrency currency, + List installments, Set charges, LocalDate recalculateChargesFrom); + } \ No newline at end of file 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 11bead0fd..cbcd0c8ee 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 @@ -11,6 +11,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -18,14 +19,13 @@ import java.util.TreeMap; import org.joda.time.Days; import org.joda.time.LocalDate; -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.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -49,22 +49,21 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator(); @Override - public LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency, - final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays) { + public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO) { final List diffAmt = null; final LocalDate prepayDate = null; - return generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, diffAmt, - prepayDate); + final LocalDate lastTransactionDate = loanApplicationTerms.getExpectedDisbursementDate(); + return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, prepayDate, lastTransactionDate); } - 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, LocalDate prepayDate) { + private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, List diffAmt, + final LocalDate prepayDate, final LocalDate lastTransactionDate) { + final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); // 1. generate list of proposed schedule due dates - final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays, - workingDays); + final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); loanApplicationTerms.updateLoanEndDate(loanEndDate); // 2. determine the total charges due at time of disbursement @@ -144,7 +143,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener isFirstRepayment); isFirstRepayment = false; scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, - isHolidayEnabled, holidays, workingDays); + holidayDetailDTO); scheduledDueDateAsPerFrequency = scheduledDueDate; } else { recalculatedInterestComponent = true; @@ -153,7 +152,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate actualRepayment = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment); scheduledDueDateAsPerFrequency = this.scheduledDateGenerator.adjustRepaymentDate(actualRepayment, loanApplicationTerms, - isHolidayEnabled, holidays, workingDays); + holidayDetailDTO); } final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); @@ -488,10 +487,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money.of(currency, loanScheduleModelPeriod.interestDue()), null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, - totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent()); + totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(), + lastTransactionDate); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, - totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent()); + totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(), + lastTransactionDate); totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount()); @@ -549,8 +550,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener @Override public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, final List holidays, - final WorkingDays workingDays) { + final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO) { final Loan loan = loanRescheduleRequest.getLoan(); final LoanSummary loanSummary = loan.getSummary(); @@ -696,8 +696,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // update the number of repayments loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); - LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays, - workingDays); + LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO); loanApplicationTerms.updateLoanEndDate(loanEndDate); if (newInterestRate != null) { @@ -739,7 +738,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms, - isHolidayEnabled, holidays, workingDays); + holidayDetailDTO); final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays(); @@ -936,7 +935,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, - boolean isInstallmentChargeApplicable) { + boolean isInstallmentChargeApplicable, final LocalDate lastTransactionDate) { Money cumulative = Money.zero(monetaryCurrency); @@ -963,7 +962,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) - && loanCharge.getChargeCalculation().isPercentageBased()) { + && loanCharge.getChargeCalculation().isPercentageBased() + && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); @@ -986,7 +986,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, - final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, boolean isInstallmentChargeApplicable) { + final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, boolean isInstallmentChargeApplicable, + final LocalDate lastTransactionDate) { Money cumulative = Money.zero(monetaryCurrency); @@ -1013,7 +1014,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) - && loanCharge.getChargeCalculation().isPercentageBased()) { + && loanCharge.getChargeCalculation().isPercentageBased() + && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); @@ -1039,11 +1041,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * of over due installment should be collected before collecting principal ) */ @Override - public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final ApplicationCurrency applicationCurrency, - final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays, final List transactions, + public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final List transactions, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final List previousSchedule, LocalDate recalculateFrom) { + final List previousSchedule, LocalDate recalculateFrom, final LocalDate lastTransactionDate, + final int penaltyWaitPeriod) { LoanScheduleModel loanScheduleModel = null; List installments = previousSchedule; @@ -1064,8 +1066,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } installments.removeAll(removeInstallments); if (previousSchedule == null) { - loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, - workingDays); + loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO); installments = retrieveRepaymentSchedule(loanScheduleModel); } @@ -1082,10 +1083,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener int installmentNumber = findLastProcessedInstallmentNumber(installments, processTransactionsForInterestCompound); List processTransactions = processTransactions(transactions, processTransactionsForInterestCompound); if (processTransactions.size() > 0) { - loanScheduleModel = createInterestOnlyRecalculationDetails(mc, applicationCurrency, loanApplicationTerms, loanCharges, - isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, - loanScheduleModel, installments, currency, recalculationDetails, retainRecalculationDetails, false, - installmentNumber, processTransactions, preCloseTransaction); + loanScheduleModel = createInterestOnlyRecalculationDetails(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, + loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, currency, + recalculationDetails, retainRecalculationDetails, false, installmentNumber, processTransactions, + preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions); } recalculationDetails.retainAll(retainRecalculationDetails.values()); processTransactionsForInterestCompound = getNextRecalculateFromDate(transactions, processTransactionsForInterestCompound); @@ -1097,10 +1098,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener List processTransactions = processTransactions(transactions, recalculateFrom); if (loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor() && processTransactions.size() > 0) { - loanScheduleModel = createInterestOnlyRecalculationDetails(mc, applicationCurrency, loanApplicationTerms, loanCharges, - isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, - loanScheduleModel, installments, currency, recalculationDetails, retainRecalculationDetails, true, - installmentNumber, processTransactions, preCloseTransaction); + loanScheduleModel = createInterestOnlyRecalculationDetails(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, + loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, currency, + recalculationDetails, retainRecalculationDetails, true, installmentNumber, processTransactions, + preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions); if (loanScheduleModel != null) { installments = retrieveRepaymentSchedule(loanScheduleModel); } @@ -1114,9 +1115,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener recalculationDetails.retainAll(retainRecalculationDetails.values()); recalculationDetails.addAll(earlypayments); - loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, - holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, - processTransactions, installmentNumber, recalculationDetails, preCloseTransaction); + loanScheduleModel = recalculateInstallment(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, + loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, processTransactions, + installmentNumber, recalculationDetails, preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions); if (loanScheduleModel != null) { installments = retrieveRepaymentSchedule(loanScheduleModel); } @@ -1130,14 +1131,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener /** * Method identifies interest only repayment periods */ - private LoanScheduleModel createInterestOnlyRecalculationDetails(final MathContext mc, final ApplicationCurrency applicationCurrency, - final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays, + private LoanScheduleModel createInterestOnlyRecalculationDetails(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final List previousSchedule, LoanScheduleModel loanScheduleModel, List installments, MonetaryCurrency currency, final List recalculationDetails, final Map retainRecalculationDetails, - boolean recalculate, int installmentNumber, List processTransactions, LoanTransaction preCloseTransaction) { + boolean recalculate, int installmentNumber, List processTransactions, LoanTransaction preCloseTransaction, + final LocalDate lastTransactionDate, final int penaltyWaitPeriod, final List actualTransactions) { LoanTransaction loanTransaction = processTransactions.remove(processTransactions.size() - 1); if (!(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver())) { processTransactions.add(loanTransaction); @@ -1187,10 +1188,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener recalculationDetails.add(recalculationDetail); if (recalculate) { recalculationDetails.retainAll(retainRecalculationDetails.values()); - loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, - isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, - loanScheduleModel, installments, processTransactions, installmentNumber, recalculationDetails, - preCloseTransaction); + loanScheduleModel = recalculateInstallment(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, + loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, + processTransactions, installmentNumber, recalculationDetails, preCloseTransaction, lastTransactionDate, + penaltyWaitPeriod, actualTransactions); } } @@ -1221,19 +1222,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param preCloseTransaction * TODO */ - private LoanScheduleModel recalculateInstallment(final MathContext mc, final ApplicationCurrency applicationCurrency, - final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays, + private LoanScheduleModel recalculateInstallment(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final List previousSchedule, LoanScheduleModel loanScheduleModel, List installments, List processTransactions, final int skipRecalculation, - final List recalculationDetails, LoanTransaction preCloseTransaction) { + final List recalculationDetails, LoanTransaction preCloseTransaction, final LocalDate lastTransactionDate, + final int penaltyWaitPeriod, final List actualTransactions) { int processInstallmentsFrom = 0; Integer lastInstallmentNumber = previousSchedule.size(); while (processInstallmentsFrom < lastInstallmentNumber) { - RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges, - isHolidayEnabled, holidays, workingDays, processTransactions, loanRepaymentScheduleTransactionProcessor, - processInstallmentsFrom, installments, recalculationDetails, skipRecalculation, preCloseTransaction); + RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, + processTransactions, loanRepaymentScheduleTransactionProcessor, processInstallmentsFrom, installments, + recalculationDetails, skipRecalculation, preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, + actualTransactions); processInstallmentsFrom = recalculatedSchedule.getInstallmentNumber(); if (recalculatedSchedule.getLoanScheduleModel() != null) { loanScheduleModel = recalculatedSchedule.getLoanScheduleModel(); @@ -1250,12 +1252,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param preCloseTransaction * TODO */ - 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, + private RecalculatedSchedule recalculateInterest(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, + final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final List transactions, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber, final List installments, final List recalculationDetails, - final int skipRecalculation, final LoanTransaction preCloseTransaction) { + final int skipRecalculation, final LoanTransaction preCloseTransaction, final LocalDate lastTransactionDate, + final int penaltyWaitPeriod, final List actualTransactions) { boolean processRecalculate = false; int processedInstallmentNumber = installmentNumber; @@ -1282,15 +1284,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener List transactionsForInstallment = new ArrayList<>(); Map recalculationDates = new HashMap<>(); LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, - isHolidayEnabled, holidays, workingDays); + holidayDetailDTO); for (LoanTransaction loanTransaction : transactions) { LocalDate loantransactionDate = loanTransaction.getTransactionDate(); if (!loantransactionDate.isAfter(transactionsDate)) { transactionsForInstallment.add(loanTransaction); - recalculationDates.put( - loantransactionDate, - getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays, - workingDays)); + recalculationDates.put(loantransactionDate, + getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO)); } } @@ -1321,15 +1321,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // this block is to identify late payment based on the rest // calculation date while (totalOutstanding.isGreaterThanZero() && startDate.isBefore(LocalDate.now())) { - LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, - holidays, workingDays); - LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, isHolidayEnabled, holidays, - workingDays); + LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); + LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, holidayDetailDTO); if (reduceStartDate) { startDate = startDate.minusDays(1); } applyRest(transactions, startDate, recalculateFrom, installment, loanRepaymentScheduleTransactionProcessor, currency, - loanApplicationTerms, isHolidayEnabled, holidays, workingDays, installments, preCloseTransaction); + loanApplicationTerms, holidayDetailDTO, installments, preCloseTransaction); totalOutstanding = installment.getTotalOutstanding(currency); if (totalOutstanding.isGreaterThanZero()) { Money latepaymentoutstanding = installment.getPrincipalOutstanding(currency); @@ -1373,12 +1371,130 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (preCloseTransaction != null) { prepayDate = preCloseTransaction.getTransactionDate(); } - model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, - recalculationDetails, prepayDate); + model = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails, prepayDate, lastTransactionDate); + model = updateOverDueCharges(loanCharges, model, lastTransactionDate, currency, loanRepaymentScheduleTransactionProcessor, + loanApplicationTerms.getExpectedDisbursementDate(), penaltyWaitPeriod, actualTransactions); } return new RecalculatedSchedule(model, processedInstallmentNumber); } + private LoanScheduleModel updateOverDueCharges(final Set loanCharges, final LoanScheduleModel model, + LocalDate lastTransactionDate, final MonetaryCurrency currency, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate disbursementDate, + final int penaltyWaitPeriod, final List transactions) { + Set chargesForUpdate = new HashSet<>(); + LoanScheduleModel loanScheduleModel = model; + for (LoanCharge loanCharge : loanCharges) { + if (loanCharge.isActive() && loanCharge.isOverdueInstallmentCharge() && loanCharge.isPenaltyCharge() + && loanCharge.getChargeCalculation().isPercentageBased() && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { + chargesForUpdate.add(loanCharge); + } + } + + if (!chargesForUpdate.isEmpty()) { + LocalDate graceDate = LocalDate.now().minusDays(penaltyWaitPeriod); + Collection periods = model.getPeriods(); + BigDecimal totalPenaltyChargesCharged = model.getTotalPenaltyChargesCharged(); + for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { + if (loanScheduleModelPeriod.isRepaymentPeriod()) { + Money overdueChargeForInstallment = overDuePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), + loanScheduleModelPeriod.periodDueDate(), chargesForUpdate, currency); + totalPenaltyChargesCharged = totalPenaltyChargesCharged.subtract(overdueChargeForInstallment.getAmount()); + BigDecimal feeChargesForInstallment = BigDecimal.ZERO; + loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment, overdueChargeForInstallment.negated().getAmount()); + } + } + + List installments = retrieveRepaymentSchedule(model); + List loanTransactions = copyTransactions(transactions); + loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(disbursementDate, loanTransactions, currency, + installments, loanCharges, lastTransactionDate); + Map installmentMap = getRepaymentsAsMap(installments); + + for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { + if (loanScheduleModelPeriod.isRepaymentPeriod()) { + LoanRepaymentScheduleInstallment installment = installmentMap.get(loanScheduleModelPeriod.periodNumber()); + Money overdueChargeForInstallment = Money.zero(currency); + overdueChargeForInstallment = cumulativeOverDuePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), + loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, installment.getPrincipalOutstanding(currency) + .getAmount(), installment.getInterestOutstanding(currency).getAmount(), graceDate.isAfter(installment + .getDueDate())); + totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(overdueChargeForInstallment.getAmount()); + BigDecimal feeChargesForInstallment = BigDecimal.ZERO; + loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment, overdueChargeForInstallment.getAmount()); + } + } + loanScheduleModel = LoanScheduleModel.withOverdueChargeUpdation(periods, model, totalPenaltyChargesCharged); + } + return loanScheduleModel; + } + + private Map getRepaymentsAsMap(List installments) { + Map installmentMap = new HashMap<>(); + for (LoanRepaymentScheduleInstallment installment : installments) { + installmentMap.put(installment.getInstallmentNumber(), installment); + } + return installmentMap; + } + + private List copyTransactions(List transactions) { + List loanTransactions = new ArrayList<>(); + for (LoanTransaction loanTransaction : transactions) { + if (loanTransaction.isNotReversed() && !loanTransaction.isAccrual()) { + LoanTransaction transaction = LoanTransaction.copyTransactionProperties(loanTransaction); + loanTransactions.add(transaction); + } + } + return loanTransactions; + } + + private Money cumulativeOverDuePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, + final Set loanCharges, final MonetaryCurrency monetaryCurrency, final BigDecimal principalOverdue, + final BigDecimal interestOverdue, final boolean recalculate) { + + Money cumulative = Money.zero(monetaryCurrency); + + for (final LoanCharge loanCharge : loanCharges) { + if (loanCharge.isPenaltyCharge()) { + if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { + BigDecimal amount = BigDecimal.ZERO; + if (recalculate) { + switch (loanCharge.getChargeCalculation()) { + case PERCENT_OF_AMOUNT: + amount = amount.add(principalOverdue); + break; + case PERCENT_OF_AMOUNT_AND_INTEREST: + amount = amount.add(principalOverdue).add(interestOverdue); + break; + case PERCENT_OF_INTEREST: + amount = amount.add(interestOverdue); + break; + default: + break; + } + BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); + cumulative = cumulative.plus(loanChargeAmt); + } + } + } + } + + return cumulative; + } + + private Money overDuePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, + final MonetaryCurrency monetaryCurrency) { + Money cumulative = Money.zero(monetaryCurrency); + for (final LoanCharge loanCharge : loanCharges) { + if (loanCharge.isPenaltyCharge()) { + if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { + cumulative = cumulative.plus(loanCharge.chargeAmount()); + } + } + } + return cumulative; + } + private List retrieveRepaymentSchedule(LoanScheduleModel model) { final List installments = new ArrayList<>(); for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) { @@ -1404,18 +1520,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener 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, final List installments, LoanTransaction preCloseTransaction) { + final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + final List installments, LoanTransaction preCloseTransaction) { List transactions = new ArrayList<>(); Map recalculationDates = new HashMap<>(); for (LoanTransaction transaction : loanTransactions) { LocalDate loantransactionDate = transaction.getTransactionDate(); if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to)) { transactions.add(transaction); - recalculationDates.put( - loantransactionDate, - getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays, - workingDays)); + recalculationDates.put(loantransactionDate, + getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO)); } } @@ -1424,11 +1538,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, - final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + final HolidayDetailDTO holidayDetailDTO) { LocalDate nextScheduleDate = null; if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate, - loanApplicationTerms, isHolidayEnabled, holidays, workingDays); + loanApplicationTerms, holidayDetailDTO); } else { CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index e777a8dd1..80026e82d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -5,24 +5,20 @@ */ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; -import java.util.List; - import org.joda.time.Days; import org.joda.time.LocalDate; import org.joda.time.Months; import org.joda.time.Weeks; import org.joda.time.Years; -import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.holiday.service.HolidayUtil; -import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override - public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays) { + public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) { final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); @@ -32,7 +28,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment); isFirstRepayment = false; } - lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays); + lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO); return lastRepaymentDate; } @@ -52,22 +48,23 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms, - final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { + final HolidayDetailDTO holidayDetailDTO) { LocalDate adjustedDate = dueRepaymentPeriodDate; /** * Fix for https://mifosforge.jira.com/browse/MIFOSX-1357 */ // recursively check for the next working day. - while (WorkingDaysUtil.isNonWorkingDay(workingDays, adjustedDate)) { + while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDate)) { final LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), adjustedDate); - adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate, workingDays); + adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate, + holidayDetailDTO.getWorkingDays()); } - if (isHolidayEnabled) { - adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidays); + if (holidayDetailDTO.isHolidayEnabled()) { + adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidayDetailDTO.getHolidays()); } return adjustedDate; } @@ -161,7 +158,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override public LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, - LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List holidays, WorkingDays workingDays) { + LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) { LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate(); boolean isFirstRepayment = true; @@ -169,7 +166,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment); isFirstRepayment = false; } - generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays); + generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO); return generatedDate; } } \ 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 17587859b..454bb332f 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 @@ -110,7 +110,7 @@ public final class LoanApplicationTerms { private BigDecimal maxOutstandingBalance; private List emiAmountVariations; - + private Money totalInterestDue; private final DaysInMonthType daysInMonthType; @@ -505,7 +505,7 @@ public final class LoanApplicationTerms { final int periodsElapsed = 0; final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed); double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments; - + return Money.of(this.principal.getCurrency(), BigDecimal.valueOf(totalRepayment)); } @@ -521,16 +521,16 @@ public final class LoanApplicationTerms { case FLAT: final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc); totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode()); - + break; case DECLINING_BALANCE: break; case INVALID: break; } - - if(this.totalInterestDue != null) { - totalInterestDue = this.totalInterestDue; + + if (this.totalInterestDue != null) { + totalInterestDue = this.totalInterestDue; } return totalInterestDue; @@ -544,7 +544,7 @@ public final class LoanApplicationTerms { final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm(); - + return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc) .multiply(loanTermFrequencyBigDecimal); } @@ -1078,59 +1078,62 @@ public final class LoanApplicationTerms { return numberOfPeriods; } - public RecalculationFrequencyType getRecalculationFrequencyType() { return this.recalculationFrequencyType; } - + public void updateNumberOfRepayments(final Integer numberOfRepayments) { - this.numberOfRepayments = numberOfRepayments; + this.numberOfRepayments = numberOfRepayments; } - + public void updatePrincipalGrace(final Integer principalGrace) { - this.principalGrace = principalGrace; + this.principalGrace = principalGrace; } - + public void updateInterestPaymentGrace(final Integer interestPaymentGrace) { - this.interestPaymentGrace = interestPaymentGrace; + this.interestPaymentGrace = interestPaymentGrace; } - + public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) { - if(interestRatePerPeriod != null) { - this.interestRatePerPeriod = interestRatePerPeriod; - } + if (interestRatePerPeriod != null) { + this.interestRatePerPeriod = interestRatePerPeriod; + } } - + public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) { - if(annualNominalInterestRate != null) { - this.annualNominalInterestRate = annualNominalInterestRate; - } + if (annualNominalInterestRate != null) { + this.annualNominalInterestRate = annualNominalInterestRate; + } } - + public BigDecimal getInterestRatePerPeriod() { - return this.interestRatePerPeriod; + return this.interestRatePerPeriod; } - + public BigDecimal getAnnualNominalInterestRate() { - return this.annualNominalInterestRate; + return this.annualNominalInterestRate; } - + public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) { - if(interestChargedFromDate != null) { - this.interestChargedFromDate = interestChargedFromDate; - } + if (interestChargedFromDate != null) { + this.interestChargedFromDate = interestChargedFromDate; + } } - + public void updateLoanTermFrequency(Integer loanTermFrequency) { - if(loanTermFrequency != null) { - this.loanTermFrequency = loanTermFrequency; - } + if (loanTermFrequency != null) { + this.loanTermFrequency = loanTermFrequency; + } } - + public void updateTotalInterestDue(Money totalInterestDue) { - - if(totalInterestDue != null) { - this.totalInterestDue = totalInterestDue; - } + + if (totalInterestDue != null) { + this.totalInterestDue = totalInterestDue; + } + } + + public ApplicationCurrency getApplicationCurrency() { + return this.currency; } } \ 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 214c04444..b624e7aa8 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 @@ -10,10 +10,9 @@ import java.util.List; import java.util.Set; import org.joda.time.LocalDate; -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.workingdays.domain.WorkingDays; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; @@ -23,19 +22,19 @@ import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanResched public interface LoanScheduleGenerator { - LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms, - Set loanCharges, boolean isHolidayEnabled, List holidays, final WorkingDays workingDays); + LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, + final HolidayDetailDTO holidayDetailDTO); - LoanScheduleModel rescheduleNextInstallments(MathContext mc, ApplicationCurrency applicationCurrency, - LoanApplicationTerms loanApplicationTerms, Set loanCharges, boolean isHolidayEnabled, List holidays, - WorkingDays workingDays, List transactions, + LoanScheduleModel rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, + final HolidayDetailDTO holidayDetailDTO, List transactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - List previousSchedule, LocalDate recalculateFrom); + List previousSchedule, LocalDate recalculateFrom, LocalDate lastTransactionDate, + int penaltyWaitPeriod); - LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, MonetaryCurrency currency, - LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, Set charges); - - LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays); + LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, + MonetaryCurrency currency, LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms, + MathContext mc, Set charges); + + LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, + final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java index 6b4b25f39..7f533c9c4 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleModel.java @@ -42,6 +42,15 @@ public final class LoanScheduleModel { totalOutstanding); } + public static LoanScheduleModel withOverdueChargeUpdation(final Collection periods, + final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) { + + return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays, + loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid, + loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, totalPenaltyChargesCharged, + loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding); + } + private LoanScheduleModel(final Collection periods, final ApplicationCurrency applicationCurrency, final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, @@ -85,4 +94,9 @@ public final class LoanScheduleModel { public Collection getPeriods() { return this.periods; } + + + public BigDecimal getTotalPenaltyChargesCharged() { + return this.totalPenaltyChargesCharged; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java index 1456e85d4..a6cb32b47 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java @@ -5,30 +5,25 @@ */ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; -import java.util.List; - import org.joda.time.LocalDate; -import org.mifosplatform.organisation.holiday.domain.Holiday; -import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; public interface ScheduledDateGenerator { - LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List holidays, - final WorkingDays workingDays); + LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO); LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, final LocalDate firstRepaymentDate); LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment); - LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, - List holidays, WorkingDays workingDays); + LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO); LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate); Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date); LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, - boolean isHolidayEnabled, List holidays, final WorkingDays workingDays); + final HolidayDetailDTO holidayDetailDTO); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 73337ecdb..4a9d6510f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -43,6 +43,7 @@ import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -358,30 +359,34 @@ public class LoanScheduleAssembler { final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final MathContext mc = new MathContext(8, roundingMode); - final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); - final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); - return loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, - workingDays); + HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + + return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO); } public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, List transactions, final Set loanCharges, final List previousSchedule, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateFrom) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateFrom, + LocalDate recalculateDueDateChargesFrom) { final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final MathContext mc = new MathContext(8, roundingMode); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository - .findOneWithNotFoundDetection(loanApplicationTerms.getCurrency()); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms .getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); - return loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency, loanApplicationTerms, loanCharges, - isHolidayEnabled, holidays, workingDays, transactions, loanRepaymentScheduleTransactionProcessor, previousSchedule, - recalculateFrom); + HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + final Long waitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + int penaltyWaitPeriod = 0; + if (waitPeriod != null) { + penaltyWaitPeriod = waitPeriod.intValue(); + } + return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions, + loanRepaymentScheduleTransactionProcessor, previousSchedule, recalculateFrom, recalculateDueDateChargesFrom, + penaltyWaitPeriod); } public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, @@ -400,8 +405,8 @@ public class LoanScheduleAssembler { CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate.minusDays(1)); if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { - nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, isHolidayEnabled, - holidays, workingDays); + HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, detailDTO); } return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, nextScheduleDate, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index e7b803a91..2e2bc8a16 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -176,7 +176,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(), modifiedTransactions, loan.charges(), loan.fetchRepaymentScheduleInstallments(), loanRepaymentScheduleTransactionProcessor, - LocalDate.now()); + LocalDate.now(), LocalDate.now()); LoanScheduleData scheduleDate = model.toData(); Collection periodDatas = scheduleDate.getPeriods(); for (LoanSchedulePeriodData periodData : periodDatas) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java index b45cd3dbf..6bd84fae3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java @@ -6,40 +6,37 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain; import java.math.MathContext; -import java.util.List; -import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; -import org.mifosplatform.organisation.workingdays.domain.WorkingDays; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory { - @Override - public LoanRescheduleModel reschedule(final MathContext mathContext, - final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays) { - - LoanRescheduleModel loanRescheduleModel = null; - - switch (interestMethod) { - case DECLINING_BALANCE: - loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule( - mathContext, loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); - break; - - case FLAT: - loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, - loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); - break; - - case INVALID: - break; - } - - return loanRescheduleModel; - } + @Override + public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, + final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, + final HolidayDetailDTO holidayDetailDTO) { + + LoanRescheduleModel loanRescheduleModel = null; + + switch (interestMethod) { + case DECLINING_BALANCE: + loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, + applicationCurrency, holidayDetailDTO); + break; + + case FLAT: + loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, + applicationCurrency, holidayDetailDTO); + break; + + case INVALID: + break; + } + + return loanRescheduleModel; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java index 6f7a196f1..b5bb26cf5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java @@ -6,16 +6,14 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain; import java.math.MathContext; -import java.util.List; -import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; -import org.mifosplatform.organisation.workingdays.domain.WorkingDays; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; public interface LoanReschedulerFactory { - - public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, - final List holidays, final WorkingDays workingDays); + + public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, + final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, + final HolidayDetailDTO holidayDetailDTO); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index f5fb48881..b6e15e69f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -18,6 +18,7 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyReposit import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; @@ -76,8 +77,9 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP final MathContext mathContext = new MathContext(8, roundingMode); List oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive( loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); + loanRescheduleRequest, applicationCurrency, holidayDetailDTO); LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel, oldPeriods); return loanRescheduleModelWithOldPeriods; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 94c10bf27..dcc50600c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -34,6 +34,7 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper; @@ -279,8 +280,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche Collection loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); + loanRescheduleRequest, applicationCurrency, holidayDetailDTO); final Collection periods = loanRescheduleModel.getPeriods(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java index 04e8525b6..493467a67 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java @@ -426,8 +426,14 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat } chargeAmount = accruableForInstallment; boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1; - if (canAddCharge && installmentChargeData.getAmountAccrued() == null) { - applicableCharges.put(loanCharge, chargeAmount); + if (canAddCharge + && (installmentChargeData.getAmountAccrued() == null || chargeAmount.compareTo(installmentChargeData + .getAmountAccrued()) != 0)) { + BigDecimal amountForAccrual = chargeAmount; + if (installmentChargeData.getAmountAccrued() != null) { + amountForAccrual = chargeAmount.subtract(installmentChargeData.getAmountAccrued()); + } + applicableCharges.put(loanCharge, amountForAccrual); BigDecimal amountAccrued = chargeAmount; if (loanCharge.getAmountAccrued() != null) { amountAccrued = amountAccrued.add(loanCharge.getAmountAccrued()); @@ -444,8 +450,12 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized()); } boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1; - if (canAddCharge && loanCharge.getAmountAccrued() == null) { - applicableCharges.put(loanCharge, chargeAmount); + if (canAddCharge && (loanCharge.getAmountAccrued() == null || chargeAmount.compareTo(loanCharge.getAmountAccrued()) != 0)) { + BigDecimal amountForAccrual = chargeAmount; + if (loanCharge.getAmountAccrued() != null) { + amountForAccrual = chargeAmount.subtract(loanCharge.getAmountAccrued()); + } + applicableCharges.put(loanCharge, amountForAccrual); } } @@ -487,7 +497,7 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat loanTransactionDatas.add(loanTransactionData); } } - + Iterator iterator = loanTransactionDatas.iterator(); for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) { if (recognized.compareTo(BigDecimal.ZERO) != 1 && unrecognized.compareTo(BigDecimal.ZERO) != 1 && iterator.hasNext()) { 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 97eb8467a..d84695520 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 @@ -86,9 +86,11 @@ import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand; +import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidByData; import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData; +import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.Loan; @@ -120,6 +122,7 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.ScheduledDate import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator; import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; +import org.mifosplatform.portfolio.loanproduct.data.LoanOverdueDTO; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException; @@ -299,20 +302,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } CalendarInstance restCalendarInstance = null; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, + allowTransactionsOnNonWorkingDay); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod, lastTransactionDate); - regenerateScheduleOnDisbursement(command, loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, - holidays, workingDays, recalculateSchedule); + regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(), loan, null); } - changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, - allowTransactionsOnNonWorkingDay, restCalendarInstance); + + changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO); } if (!changes.isEmpty()) { saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -496,19 +506,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.getLoanTransactions().add(disbursementTransaction); } CalendarInstance restCalendarInstance = null; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } - regenerateScheduleOnDisbursement(command, loan, applicationCurrency, firstRepaymentOnDate, isHolidayEnabled, holidays, - workingDays, recalculateSchedule); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, + allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); + final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + firstRepaymentOnDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); + regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive( loan.fetchRepaymentScheduleInstallments(), loan, null); } - changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes, - firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, - allowTransactionsOnNonWorkingDay, restCalendarInstance); + + changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO); } if (!changes.isEmpty()) { @@ -582,8 +599,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); - final Map changes = loan.undoDisbursal(this.loanScheduleFactory, applicationCurrency, existingTransactionIds, - existingReversedTransactionIds, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO); + + final Map changes = loan + .undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds); if (!changes.isEmpty()) { saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -740,6 +761,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf ApplicationCurrency applicationCurrency = null; LocalDate calculatedRepaymentsStartingFromDate = null; boolean isHolidayEnabled = false; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); @@ -752,12 +777,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.getDisbursementDate(), loan, calendarInstance); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, + allowTransactionsOnNonWorkingDay); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); + final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, - allowTransactionsOnHoliday, holidays, workingDays, isHolidayEnabled, allowTransactionsOnNonWorkingDay, - this.loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); + scheduleGeneratorDTO); if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { if (paymentDetail != null) { @@ -856,6 +887,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf List holidays = null; boolean isHolidayEnabled = false; WorkingDays workingDays = null; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); @@ -870,12 +904,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); workingDays = this.workingDaysRepository.findOne(); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } - final ChangedTransactionDetail changedTransactionDetail = loan - .waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, - existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, this.loanScheduleFactory, - applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); + final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, + defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); this.loanTransactionRepository.save(waiveInterestTransaction); @@ -949,14 +986,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); CalendarInstance restCalendarInstance = null; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), - changes, existingTransactionIds, existingReversedTransactionIds, currentUser, this.loanScheduleFactory, - applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance); + changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); LoanTransaction writeoff = changedTransactionDetail.getNewTransactionMappings().remove(0L); this.loanTransactionRepository.save(writeoff); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { @@ -1017,14 +1061,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); CalendarInstance restCalendarInstance = null; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); - + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, - existingTransactionIds, existingReversedTransactionIds, this.loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance); + existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO); final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); if (possibleClosingTransaction != null) { this.loanTransactionRepository.save(possibleClosingTransaction); @@ -1133,8 +1185,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null, chargeDefinition.getName()); } - addCharge(loan, chargeDefinition, loanCharge); + final List existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds()); + final List existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds()); + boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); + + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) { + runScheduleRecalculation(loan); + } + updateOriginalSchedule(loan); + + } + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && isAppliedOnBackDate + && loan.isFeeCompoundingEnabledForInterestRecalculation()) { + this.loanAccountDomainService.recalculateAccruals(loan); + } return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(loanCharge.getId()) // @@ -1145,7 +1214,71 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } - private void addCharge(final Loan loan, final Charge chargeDefinition, final LoanCharge loanCharge) { + public void runScheduleRecalculation(Loan loan) { + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + + final MonetaryCurrency currency = loan.getCurrency(); + ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), + CalendarEntityType.LOANS.getValue()); + LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + loan.getDisbursementDate(), loan, calendarInstance); + + boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate() + .toDate()); + WorkingDays workingDays = this.workingDaysRepository.findOne(); + final Long overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); + + LocalDate recalculateFrom = null; + LocalDate lastTransactionDate = null; + + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod, lastTransactionDate); + ChangedTransactionDetail changedTransactionDetail = loan + .handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO); + saveLoanWithDataIntegrityViolationChecks(loan); + if (changedTransactionDetail != null) { + for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { + this.loanTransactionRepository.save(mapEntry.getValue()); + // update loan with references to the newly created + // transactions + loan.getLoanTransactions().add(mapEntry.getValue()); + this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); + } + } + + } + } + + public void updateOriginalSchedule(Loan loan) { + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + + final MonetaryCurrency currency = loan.getCurrency(); + ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), + CalendarEntityType.LOANS.getValue()); + LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + loan.getDisbursementDate(), loan, calendarInstance); + + boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate() + .toDate()); + WorkingDays workingDays = this.workingDaysRepository.findOne(); + + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO); + createLoanScheduleArchive(loan, scheduleGeneratorDTO); + } + + } + + private boolean addCharge(final Loan loan, final Charge chargeDefinition, final LoanCharge loanCharge) { if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) { final String errorMessage = "Charge and Loan must have the same currency."; @@ -1161,34 +1294,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - final List existingTransactionIds = new ArrayList<>(); - final List existingReversedTransactionIds = new ArrayList<>(); this.loanChargeRepository.save(loanCharge); - 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.addLoanCharge(loanCharge, existingTransactionIds, - existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, restCalendarInstance); + final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge); /** * we want to apply charge transactions only for those loans charges @@ -1217,13 +1325,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); } } - if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, - workingDays); - } - // we post Journal entries only for loans in active status - postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + } + boolean isAppliedOnBackDate = false; + if (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate())) { + isAppliedOnBackDate = true; + } + return isAppliedOnBackDate; } @Transactional @@ -1311,6 +1419,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf List holidays = null; boolean isHolidayEnabled = false; WorkingDays workingDays = null; + LocalDate recalculateFrom = null; + LocalDate lastTransactionDate = null; + Long overdurPenaltyWaitPeriod = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); @@ -1325,8 +1436,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); workingDays = this.workingDaysRepository.findOne(); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); + Money accruedCharge = Money.zero(loan.getCurrency()); if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { Collection chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy( @@ -1337,8 +1454,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes, - existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, holidays, workingDays, isHolidayEnabled, - loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance, accruedCharge); + existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge); this.loanTransactionRepository.save(waiveTransaction); saveLoanWithDataIntegrityViolationChecks(loan); @@ -2011,39 +2127,43 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (!overdueLoanScheduledInstallments.isEmpty()) { final StringBuilder sb = new StringBuilder(); + final Map> overdueScheduleData = new HashMap<>(); for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduledInstallments) { + if (overdueScheduleData.containsKey(overdueInstallment.getLoanId())) { + overdueScheduleData.get(overdueInstallment.getLoanId()).add(overdueInstallment); + } else { + Collection loanData = new ArrayList<>(); + loanData.add(overdueInstallment); + overdueScheduleData.put(overdueInstallment.getLoanId(), loanData); + } + } + + for (final Long loanId : overdueScheduleData.keySet()) { try { - - final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString()); - - final JsonCommand command = JsonCommand.from(overdueInstallment.toString(), parsedCommand, this.fromApiJsonHelper, - null, null, null, null, null, overdueInstallment.getLoanId(), null, null, null, null); - - applyChargeToOverdueLoanInstallment(overdueInstallment.getLoanId(), overdueInstallment.getChargeId(), - overdueInstallment.getPeriodNumber(), command); + applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId)); } catch (final PlatformApiDataValidationException e) { final List errors = e.getErrors(); for (final ApiParameterError error : errors) { - logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId() - + " with message " + error.getDeveloperMessage()); - sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId()) - .append(" with message ").append(error.getDeveloperMessage()); + logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " + + error.getDeveloperMessage()); + sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") + .append(error.getDeveloperMessage()); } } catch (final AbstractPlatformDomainRuleException ex) { - logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId() - + " with message " + ex.getDefaultUserMessage()); - sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId()) - .append(" with message ").append(ex.getDefaultUserMessage()); + logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " + + ex.getDefaultUserMessage()); + sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") + .append(ex.getDefaultUserMessage()); } catch (Exception e) { Throwable realCause = e; if (e.getCause() != null) { realCause = e.getCause(); } - logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId() - + " with message " + realCause.getMessage()); - sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId()) - .append(" with message ").append(realCause.getMessage()); + logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message " + + realCause.getMessage()); + sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ") + .append(realCause.getMessage()); } } if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } @@ -2051,9 +2171,42 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } @Transactional - public void applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber, - final JsonCommand command) { + public void applyOverdueChargesForLoan(final Long loanId, Collection overdueLoanScheduleDatas) { + Loan loan = null; + final List existingTransactionIds = new ArrayList<>(); + final List existingReversedTransactionIds = new ArrayList<>(); + boolean runInterestRecalculation = false; + for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduleDatas) { + + final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString()); + final JsonCommand command = JsonCommand.from(overdueInstallment.toString(), parsedCommand, this.fromApiJsonHelper, null, null, + null, null, null, loanId, null, null, null, null); + LoanOverdueDTO overdueDTO = applyChargeToOverdueLoanInstallment(loanId, overdueInstallment.getChargeId(), + overdueInstallment.getPeriodNumber(), command, loan, existingTransactionIds, existingReversedTransactionIds); + loan = overdueDTO.getLoan(); + runInterestRecalculation = runInterestRecalculation || overdueDTO.isRunInterestRecalculation(); + } + if (loan != null) { + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) { + runScheduleRecalculation(loan); + } + updateOriginalSchedule(loan); + } + + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && runInterestRecalculation + && loan.isFeeCompoundingEnabledForInterestRecalculation()) { + this.loanAccountDomainService.recalculateAccruals(loan); + } + } + } + + public LoanOverdueDTO applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber, + final JsonCommand command, Loan loan, final List existingTransactionIds, final List existingReversedTransactionIds) { + boolean runInterestRecalculation = false; final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(loanChargeId); Collection frequencyNumbers = loanChargeReadPlatformService.retrieveOverdueInstallmentChargeFrequencyNumber(loanId, @@ -2087,24 +2240,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf scheduleDates.remove(frequency); } - Loan loan = null; LoanRepaymentScheduleInstallment installment = null; if (!scheduleDates.isEmpty()) { - loan = this.loanAssembler.assembleFrom(loanId); - checkClientOrGroupActive(loan); + if (loan == null) { + loan = this.loanAssembler.assembleFrom(loanId); + checkClientOrGroupActive(loan); + existingTransactionIds.addAll(loan.findExistingTransactionIds()); + existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); + } installment = loan.fetchRepaymentScheduleInstallment(periodNumber); } + if (loan != null) { - for (Map.Entry entry : scheduleDates.entrySet()) { + for (Map.Entry entry : scheduleDates.entrySet()) { - final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue()); + final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue()); - LoanOverdueInstallmentCharge overdueInstallmentCharge = new LoanOverdueInstallmentCharge(loanCharge, installment, - entry.getKey()); - loanCharge.updateOverdueInstallmentCharge(overdueInstallmentCharge); + LoanOverdueInstallmentCharge overdueInstallmentCharge = new LoanOverdueInstallmentCharge(loanCharge, installment, + entry.getKey()); + loanCharge.updateOverdueInstallmentCharge(overdueInstallmentCharge); - addCharge(loan, chargeDefinition, loanCharge); + boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge); + runInterestRecalculation = runInterestRecalculation || isAppliedOnBackDate; + } } + + return new LoanOverdueDTO(loan, runInterestRecalculation); } @Override @@ -2122,6 +2283,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf List holidays = null; boolean isHolidayEnabled = false; WorkingDays workingDays = null; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); @@ -2136,11 +2301,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); workingDays = this.workingDaysRepository.findOne(); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); + ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, - isHolidayEnabled, holidays, workingDays, this.loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, restCalendarInstance); + scheduleGeneratorDTO); if (changedTransactionDetail != null) { for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { this.loanTransactionRepository.save(mapEntry.getValue()); @@ -2182,13 +2352,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingReversedTransactionIds = new ArrayList<>(); final Map changes = new LinkedHashMap<>(); CalendarInstance restCalendarInstance = null; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); + final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command, - existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance); + existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO); saveAndFlushLoanWithDataIntegrityViolationChecks(loan); @@ -2198,8 +2377,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, - workingDays); + createLoanScheduleArchive(loan, scheduleGeneratorDTO); } postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); this.loanAccountDomainService.recalculateAccruals(loan); @@ -2299,6 +2477,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf List holidays = null; boolean isHolidayEnabled = false; WorkingDays workingDays = null; + LocalDate recalculateFrom = null; + Long overdurPenaltyWaitPeriod = null; + LocalDate lastTransactionDate = null; final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { @@ -2315,9 +2496,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); workingDays = this.workingDaysRepository.findOne(); + overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } - ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(loanScheduleFactory, - applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance, + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, + lastTransactionDate); + + ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO, existingTransactionIds, existingReversedTransactionIds); saveLoanWithDataIntegrityViolationChecks(loan); @@ -2340,23 +2526,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } } - private void createLoanScheduleArchive(final Loan loan, final ApplicationCurrency applicationCurrency, - final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List holidays, - final WorkingDays workingDays) { - LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); + private void createLoanScheduleArchive(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) { + LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO); List installments = retrieveRepaymentScheduleFromModel(loanScheduleModel); this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(installments, loan, null); } - private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan, - final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, - final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays, final boolean recalculateSchedule) { + private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan, final boolean recalculateSchedule, + final ScheduleGeneratorDTO scheduleGeneratorDTO) { final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName); - loan.regenerateScheduleOnDisbursement(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, - isHolidayEnabled, holidays, workingDays, recalculateSchedule, actualDisbursementDate, emiAmount); + loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount); } private List retrieveRepaymentScheduleFromModel(LoanScheduleModel model) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanOverdueDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanOverdueDTO.java new file mode 100755 index 000000000..48c9901ae --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanOverdueDTO.java @@ -0,0 +1,22 @@ +package org.mifosplatform.portfolio.loanproduct.data; + +import org.mifosplatform.portfolio.loanaccount.domain.Loan; + +public class LoanOverdueDTO { + + private final Loan loan; + private final boolean runInterestRecalculation; + + public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation) { + this.loan = loan; + this.runInterestRecalculation = runInterestRecalculation; + } + + public boolean isRunInterestRecalculation() { + return this.runInterestRecalculation; + } + + public Loan getLoan() { + return this.loan; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java index 4913a6227..ebfea5518 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java @@ -52,4 +52,9 @@ public enum InterestRecalculationCompoundingMethod { public String getCode() { return this.code; } + + public boolean isFeeCompoundingEnabled() { + return this.getValue().equals(InterestRecalculationCompoundingMethod.FEE.getValue()) + || this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue()); + } }