From ff84a13c4c71a62be05fe4f325e3c55cce6da180 Mon Sep 17 00:00:00 2001 From: Pramod Nuthakki Date: Wed, 22 Apr 2015 20:32:02 +0530 Subject: [PATCH] MIFOSX-1951 : compounding changes and recalculation fixes --- api-docs/apiLive.htm | 62 ++ .../ClientLoanIntegrationTest.java | 105 +++- .../loans/LoanApplicationTestBuilder.java | 13 +- .../common/loans/LoanProductTestBuilder.java | 25 +- .../core/data/DataValidatorBuilder.java | 53 +- .../calendar/api/CalendarsApiResource.java | 2 +- .../calendar/domain/CalendarEntityType.java | 3 +- .../loanaccount/api/LoansApiResource.java | 19 +- .../loanaccount/data/LoanAccountData.java | 39 +- .../data/LoanInterestRecalculationData.java | 19 +- .../data/ScheduleGeneratorDTO.java | 20 +- .../portfolio/loanaccount/domain/Loan.java | 171 +++--- .../domain/LoanAccountDomainServiceJpa.java | 17 +- .../LoanInterestRecalculationDetails.java | 64 +- ...oanRepaymentScheduleProcessingWrapper.java | 21 +- .../loanaccount/domain/LoanSummary.java | 40 +- ...RepaymentScheduleTransactionProcessor.java | 22 +- ...RepaymentScheduleTransactionProcessor.java | 5 +- .../domain/AbstractLoanScheduleGenerator.java | 578 +++++++++++------- ...gBalanceInterestLoanScheduleGenerator.java | 40 +- .../FlatInterestLoanScheduleGenerator.java | 4 +- .../domain/LoanApplicationTerms.java | 45 +- .../domain/LoanScheduleGenerator.java | 7 +- .../service/LoanScheduleAssembler.java | 42 +- ...cheduleCalculationPlatformServiceImpl.java | 11 +- .../domain/DefaultLoanReschedulerFactory.java | 7 +- .../domain/LoanReschedulerFactory.java | 2 +- ...nReschedulePreviewPlatformServiceImpl.java | 7 +- ...heduleRequestWritePlatformServiceImpl.java | 17 +- ...anApplicationCommandFromApiJsonHelper.java | 50 +- ...WritePlatformServiceJpaRepositoryImpl.java | 200 +++--- .../loanaccount/service/LoanAssembler.java | 6 +- .../service/LoanReadPlatformServiceImpl.java | 27 +- ...WritePlatformServiceJpaRepositoryImpl.java | 126 ++-- .../loanproduct/LoanProductConstants.java | 3 + .../loanproduct/data/LoanProductData.java | 28 +- .../LoanProductInterestRecalculationData.java | 27 +- ...anProductInterestRecalculationDetails.java | 110 +++- .../domain/RecalculationFrequencyType.java | 21 + .../LoanProductDataValidator.java | 88 ++- .../LoanProductReadPlatformServiceImpl.java | 16 +- .../meeting/api/MeetingsApiResource.java | 2 +- .../V258__interest_compounding_changes.sql | 10 + 43 files changed, 1502 insertions(+), 672 deletions(-) create mode 100644 mifosng-provider/src/main/resources/sql/migrations/core_db/V258__interest_compounding_changes.sql diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 5095ce040..05413d06f 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -7474,6 +7474,13 @@ An enumeration that indicates the type of transaction processing strategy to be Specifies rest frequency start date for interest recalculation. This date must be before or equal to disbursement date + + recalculationCompoundingFrequencyDate + + + Specifies compounding frequency start date for interest recalculation. This date must be equal to disbursement date + + @@ -7649,6 +7656,11 @@ GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1& "code": "interestRecalculationCompoundingMethod.fee", "value": "Fee" }, + "recalculationCompoundingFrequencyType": { + "id":1, + "code":"interestRecalculationFrequencyType.same.as.repayment.period", + "value":"Same as repayment period" + }, "rescheduleStrategyType": { "id": 2, "code": "loanRescheduleStrategyMethod.reduce.number.of.installments", @@ -8608,6 +8620,14 @@ Request Body: recalculationRestFrequencyDate + + + + + + + +
Additional Mandatory Fields if interest recalculation with interest/fee compounding is enabled for product and compounding frequency not same as repayment period
recalculationCompoundingFrequencyDate

@@ -11705,6 +11725,26 @@ An enumeration that indicates the type of transaction processing strategy to be + + + + + + + + + + + + + + + + + @@ -11848,6 +11888,11 @@ GET https://DomainName/api/v1/loanproducts "code": "interestRecalculationCompoundingMethod.fee", "value": "Fee" }, + "recalculationCompoundingFrequencyType": { + "id":1, + "code":"interestRecalculationFrequencyType.same.as.repayment.period", + "value":"Same as repayment period" + }, "rescheduleStrategyType": { "id": 2, "code": "loanRescheduleStrategyMethod.reduce.number.of.installments", @@ -13058,6 +13103,23 @@ GET https://DomainName/api/v1/loanproducts/{productId}
Specifies what action should perform on loan repayment schedule for advance payments.
Example Values:1=Reschedule next repayments, 2=Reduce number of installments, 3=Reduce EMI amount
recalculationCompoundingFrequencyType
Specifies effective date from which the compounding of interest or fee amounts will be considered in recalculation on late payment.
+ Example Values:1=Same as repayment period, 2=Daily, 3=Weekly, 4=Monthly
recalculationCompoundingFrequencyInterval
Specifies compounding frequency interval for interest recalculation.
+
recalculationCompoundingFrequencyDate
Specifies compounding frequency start date for interest recalculation.
+
recalculationRestFrequencyType
recalculationRestFrequencyInterval, recalculationRestFrequencyDate
+ + + + + + + +
Additional Optional Fields if interest recalculation is enabled(true) and interestRecalculationCompoundingMethod is enabled
recalculationCompoundingFrequencyType
+ + + + + + + +
Additional Optional Fields if interest recalculation is enabled(true) and interestRecalculationCompoundingMethod is enabled and + recalculationCompoundingFrequencyType is not same as repayment period
recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate
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 18739a230..09420415b 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java @@ -3262,7 +3262,7 @@ public class ClientLoanIntegrationTest { @Test public void testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI_PRE_CLOSE_INTEREST_REST_DATE() { String preCloseInterestStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_REST_DATE; - String preCloseAmount = "7578.4"; + String preCloseAmount = "7586.62"; testLoanScheduleWithInterestRecalculation_WITH_REST_SAME_AS_REPAYMENT_INTEREST_COMPOUND_NONE_STRATEGY_REDUCE_EMI_PRE_CLOSE_INTEREST( preCloseInterestStrategy, preCloseAmount); } @@ -3314,8 +3314,8 @@ public class ClientLoanIntegrationTest { todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "46.15", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2499.12", "29.79", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2535.36", "11.7", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.76", "46.15", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2551.72", "11.78", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); @@ -3330,8 +3330,8 @@ public class ClientLoanIntegrationTest { todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "0.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2502.44", "26.47", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2520.58", "11.63", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2528.8", "11.67", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); @@ -3453,14 +3453,15 @@ public class ClientLoanIntegrationTest { final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); - final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY, - LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST, + final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails( + 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, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", LOAN_DISBURSEMENT_DATE, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, - LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0)); + LOAN_DISBURSEMENT_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0)); Assert.assertNotNull(loanID); HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); @@ -3580,14 +3581,15 @@ public class ClientLoanIntegrationTest { 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, + final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails( + 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, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", LOAN_DISBURSEMENT_DATE, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, - REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, charges); + REST_START_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, charges); Assert.assertNotNull(loanID); HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); @@ -3615,9 +3617,9 @@ public class ClientLoanIntegrationTest { expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.57", "46.34", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.49", "31.42", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2537.18", "19.95", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.08", "46.83", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2488.82", "40.09", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2546.34", "19.96", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); @@ -3664,7 +3666,7 @@ public class ClientLoanIntegrationTest { @Test public void testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST_PRE_CLOSE_DATE() { String preCloseInterestStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE; - String preCloseAmount = "7766.75"; + String preCloseAmount = "7766.81"; testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST( preCloseInterestStrategy, preCloseAmount); @@ -3673,7 +3675,7 @@ public class ClientLoanIntegrationTest { @Test public void testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST_REST_DATE() { String preCloseInterestStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_REST_DATE; - String preCloseAmount = "7771.71"; + String preCloseAmount = "7771.79"; testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST( preCloseInterestStrategy, preCloseAmount); @@ -3709,10 +3711,11 @@ public class ClientLoanIntegrationTest { 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, + final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails( + 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, preCloseInterestStrategy, null); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", REST_START_DATE, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, null, null, preCloseInterestStrategy, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, charges); @@ -3743,9 +3746,9 @@ public class ClientLoanIntegrationTest { expectedvalues = new ArrayList<>(); todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "100.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.37", "46.54", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.29", "31.62", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2537.58", "16.65", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2482.08", "46.83", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2495.47", "33.44", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2539.69", "16.66", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); @@ -3760,8 +3763,8 @@ public class ClientLoanIntegrationTest { todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "100.0", "0.0"); addRepaymentValues(expectedvalues, todaysDate, 1, false, "2489.31", "39.6", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2500.78", "28.13", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.15", "16.61", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2500.7", "28.21", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.23", "16.61", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); @@ -3797,11 +3800,15 @@ public class ClientLoanIntegrationTest { final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final String recalculationCompoundingFrequencyInterval = null; + final String recalculationCompoundingFrequencyDate = null; 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, - LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, overdueFeeChargeId.toString(), false); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, recalculationCompoundingFrequencyInterval, + recalculationCompoundingFrequencyDate, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, + overdueFeeChargeId.toString(), false); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, null); @@ -4001,10 +4008,11 @@ public class ClientLoanIntegrationTest { final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); - final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY, - LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST, + final Integer loanProductID = createLoanProductWithInterestRecalculationAndCompoundingDetails( + LoanProductTestBuilder.RBI_INDIA_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST, LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "1", LOAN_DISBURSEMENT_DATE, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, @@ -4080,11 +4088,15 @@ public class ClientLoanIntegrationTest { final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final String recalculationCompoundingFrequencyInterval = null; + final String recalculationCompoundingFrequencyDate = null; final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST, LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, - LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null, true); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, recalculationCompoundingFrequencyInterval, + recalculationCompoundingFrequencyDate, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null, + true); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0)); @@ -4173,16 +4185,34 @@ public class ClientLoanIntegrationTest { final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, final String recalculationRestFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts) { + final String recalculationCompoundingFrequencyType = null; + final String recalculationCompoundingFrequencyInterval = null; + final String recalculationCompoundingFrequencyDate = null; return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, - recalculationRestFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false); + recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, + recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false); + } + + private Integer createLoanProductWithInterestRecalculationAndCompoundingDetails(final String repaymentStrategy, + final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, + final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, + final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType, + final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate, + final String preCloseInterestCalculationStrategy, final Account[] accounts) { + return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, + rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, + recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, + recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false); } private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy, final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, - final String recalculationRestFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts, - final String chargeId, boolean isArrearsBasedOnOriginalSchedule) { + final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType, + final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate, + final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId, + boolean isArrearsBasedOnOriginalSchedule) { System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); LoanProductTestBuilder builder = new LoanProductTestBuilder() .withPrincipal("10000000.00") @@ -4197,7 +4227,9 @@ public class ClientLoanIntegrationTest { .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, preCloseInterestCalculationStrategy) .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval, - recalculationRestFrequencyDate); + recalculationRestFrequencyDate) + .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType, + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate); if (accounts != null) { builder = builder.withAccountingRulePeriodicAccrual(accounts); } @@ -4210,6 +4242,14 @@ public class ClientLoanIntegrationTest { private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, final String disbursementDate, final String restStartDate, final String repaymentStrategy, final List charges) { + final String compoundingStartDate = null; + return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate, + compoundingStartDate, repaymentStrategy, charges); + } + + private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, + final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy, + final List charges) { System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); final String loanApplicationJSON = new LoanApplicationTestBuilder() // .withPrincipal("10000.00") // @@ -4225,6 +4265,7 @@ public class ClientLoanIntegrationTest { .withExpectedDisbursementDate(disbursementDate) // .withSubmittedOnDate(disbursementDate) // .withRestFrequencyDate(restStartDate)// + .withCompoundingFrequencyDate(compoundingStartDate)// .withwithRepaymentStrategy(repaymentStrategy) // .withCharges(charges)// .build(clientID.toString(), loanProductID.toString(), null); diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java index c46dfa3ef..6c4e3fe96 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -47,6 +47,7 @@ public class LoanApplicationTestBuilder { private List disbursementData = null; private List charges = new ArrayList(); private String recalculationRestFrequencyDate = null; + private String recalculationCompoundingFrequencyDate = null; private String repaymentsStartingFromDate = null; public String build(final String ID, final String loanProductId, final String savingsID) { @@ -74,7 +75,7 @@ public class LoanApplicationTestBuilder { map.put("expectedDisbursementDate", this.expectedDisbursmentDate); map.put("submittedOnDate", this.submittedOnDate); map.put("loanType", this.loanType); - if(repaymentsStartingFromDate != null){ + if (repaymentsStartingFromDate != null) { map.put("repaymentsStartingFromDate", this.repaymentsStartingFromDate); } if (charges != null) { @@ -101,6 +102,9 @@ public class LoanApplicationTestBuilder { if (recalculationRestFrequencyDate != null) { map.put("recalculationRestFrequencyDate", recalculationRestFrequencyDate); } + if (recalculationCompoundingFrequencyDate != null) { + map.put("recalculationCompoundingFrequencyDate", recalculationCompoundingFrequencyDate); + } System.out.println("Loan Application request : " + map); return new Gson().toJson(map); } @@ -244,7 +248,12 @@ public class LoanApplicationTestBuilder { this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; return this; } - + + public LoanApplicationTestBuilder withCompoundingFrequencyDate(final String recalculationCompoundingFrequencyDate) { + this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate; + return this; + } + public LoanApplicationTestBuilder withFirstRepaymentDate(final String firstRepaymentDate) { this.repaymentsStartingFromDate = firstRepaymentDate; return this; diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java index d5352b194..455e58bd4 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java @@ -92,6 +92,9 @@ public class LoanProductTestBuilder { private String recalculationRestFrequencyType = "1"; private String recalculationRestFrequencyInterval = "0"; private String recalculationRestFrequencyDate = null; + private String recalculationCompoundingFrequencyType = null; + private String recalculationCompoundingFrequencyInterval = null; + private String recalculationCompoundingFrequencyDate = null; private String minimumDaysBetweenDisbursalAndFirstRepayment = null; private Boolean holdGuaranteeFunds = null; private String mandatoryGuarantee = null; @@ -101,7 +104,7 @@ public class LoanProductTestBuilder { private String graceOnPrincipalPayment = "1"; private String graceOnInterestPayment = "1"; private JsonObject allowAttributeOverrides = null; - + public String build(final String chargeId) { final HashMap map = new HashMap<>(); @@ -157,6 +160,11 @@ public class LoanProductTestBuilder { map.put("recalculationRestFrequencyType", recalculationRestFrequencyType); map.put("recalculationRestFrequencyInterval", recalculationRestFrequencyInterval); map.put("recalculationRestFrequencyDate", recalculationRestFrequencyDate); + if (!RECALCULATION_COMPOUNDING_METHOD_NONE.equals(this.interestRecalculationCompoundingMethod)) { + map.put("recalculationCompoundingFrequencyType", recalculationCompoundingFrequencyType); + map.put("recalculationCompoundingFrequencyInterval", recalculationCompoundingFrequencyInterval); + map.put("recalculationCompoundingFrequencyDate", recalculationCompoundingFrequencyDate); + } map.put("preClosureInterestCalculationStrategy", preCloseInterestCalculationStrategy); if (isArrearsBasedOnOriginalSchedule != null) { map.put("isArrearsBasedOnOriginalSchedule", isArrearsBasedOnOriginalSchedule); @@ -172,8 +180,8 @@ public class LoanProductTestBuilder { } map.put("graceOnPrincipalPayment", graceOnPrincipalPayment); map.put("graceOnInterestPayment", graceOnInterestPayment); - if(allowAttributeOverrides != null){ - map.put("allowAttributeOverrides", this.allowAttributeOverrides); + if (allowAttributeOverrides != null) { + map.put("allowAttributeOverrides", this.allowAttributeOverrides); } return new Gson().toJson(map); } @@ -411,6 +419,15 @@ public class LoanProductTestBuilder { this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; return this; } + + public LoanProductTestBuilder withInterestRecalculationCompoundingFrequencyDetails(final String recalculationCompoundingFrequencyType, + final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate) { + this.isInterestRecalculationEnabled = true; + this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType; + this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval; + this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate; + return this; + } public LoanProductTestBuilder withMinimumDaysBetweenDisbursalAndFirstRepayment(final String minimumDaysBetweenDisbursalAndFirstRepayment) { this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment; @@ -440,5 +457,5 @@ public class LoanProductTestBuilder { public LoanProductTestBuilder withLoanProductConfiguration(JsonObject loanProductConfigurableAttributes) { this.allowAttributeOverrides = loanProductConfigurableAttributes; return this; - } + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java index 870ffa111..91a90f830 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/core/data/DataValidatorBuilder.java @@ -129,8 +129,9 @@ public class DataValidatorBuilder { /*** FIXME: Vishwas, why does this method have a parameter? Seems wrong ***/ /* - * This method is not meant for validation, if you have mandatory boolean param and - * if it has invalid value or value not passed then call this method, this method is always used with input as false + * This method is not meant for validation, if you have mandatory boolean + * param and if it has invalid value or value not passed then call this + * method, this method is always used with input as false */ public DataValidatorBuilder trueOrFalseRequired(final boolean trueOfFalseFieldProvided) { if (!trueOfFalseFieldProvided && !this.ignoreNullValue) { @@ -385,7 +386,7 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder integerEqualToOrGreaterThanNumber(Integer number) { if (this.value == null && this.ignoreNullValue) { return this; } @@ -394,8 +395,8 @@ public class DataValidatorBuilder { if (intValue < number) { final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") .append(this.parameter).append(".not.equal.to.or.greater.than.specified.number"); - final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter).append( - " must be equal to or greater than").append(number); + final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter) + .append(" must be equal to or greater than").append(number); final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), defaultEnglishMessage.toString(), this.parameter, intValue, number); this.dataValidationErrors.add(error); @@ -403,7 +404,7 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder integerSameAsNumber(Integer number) { if (this.value == null && this.ignoreNullValue) { return this; } @@ -422,6 +423,24 @@ public class DataValidatorBuilder { return this; } + public DataValidatorBuilder integerInMultiplesOfNumber(Integer number) { + if (this.value == null && this.ignoreNullValue) { return this; } + + if (this.value != null) { + final Integer intValue = Integer.valueOf(this.value.toString()); + if (intValue < number || intValue % number != 0) { + final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") + .append(this.parameter).append(".not.in.multiples.of.specified.number"); + final StringBuilder defaultEnglishMessage = new StringBuilder("The parameter ").append(this.parameter) + .append(" must be multiples of ").append(number); + final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), this.parameter, intValue, number); + this.dataValidationErrors.add(error); + } + } + return this; + } + /* * should be used with .notNull() before it */ @@ -822,7 +841,7 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder validateDateBefore(final LocalDate date) { if (this.value == null && this.ignoreNullValue) { return this; } @@ -840,7 +859,7 @@ public class DataValidatorBuilder { } return this; } - + public DataValidatorBuilder validateDateBeforeOrEqual(final LocalDate date) { if (this.value == null && this.ignoreNullValue) { return this; } @@ -859,4 +878,22 @@ public class DataValidatorBuilder { return this; } + public DataValidatorBuilder validateDateForEqual(final LocalDate date) { + if (this.value == null && this.ignoreNullValue) { return this; } + + if (this.value != null && date != null) { + final LocalDate dateVal = (LocalDate) this.value; + if (!dateVal.isEqual(date)) { + final StringBuilder validationErrorCode = new StringBuilder("validation.msg.").append(this.resource).append(".") + .append(this.parameter).append(".is.not.equal.to.date"); + final StringBuilder defaultEnglishMessage = new StringBuilder("The ").append(this.parameter) + .append(" must be equal to provided date").append(date); + final ApiParameterError error = ApiParameterError.parameterError(validationErrorCode.toString(), + defaultEnglishMessage.toString(), this.parameter, dateVal, date); + this.dataValidationErrors.add(error); + } + } + return this; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java index 4b74b5bfe..4a45ce22f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/api/CalendarsApiResource.java @@ -241,7 +241,7 @@ public class CalendarsApiResource { break; case INVALID: break; - case LOAN_RECALCULATION_DETAIL: + case LOAN_RECALCULATION_REST_DETAIL: break; default: break; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java index bec1bfb02..d30b16033 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java @@ -16,7 +16,8 @@ public enum CalendarEntityType { LOANS(3, "calendarEntityType.loans"), // CENTERS(4, "calendarEntityType.centers"), // SAVINGS(5, "calendarEntityType.savings"), // - LOAN_RECALCULATION_DETAIL(6, "calendarEntityType.loan.recalculation.detail"); + LOAN_RECALCULATION_REST_DETAIL(6, "calendarEntityType.loan.recalculation.rest.detail"), // + LOAN_RECALCULATION_COMPOUNDING_DETAIL(7, "calendarEntityType.loan.recalculation.compounding.detail"); private final Integer value; private final String code; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index 2b3dbe8d5..b374963c6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -226,8 +226,7 @@ public class LoansApiResource { public String template(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId, @QueryParam("productId") final Long productId, @QueryParam("templateType") final String templateType, @DefaultValue("false") @QueryParam("staffInSelectedOfficeOnly") final boolean staffInSelectedOfficeOnly, - @DefaultValue("false") @QueryParam("activeOnly") final boolean onlyActive, - @Context final UriInfo uriInfo) { + @DefaultValue("false") @QueryParam("activeOnly") final boolean onlyActive, @Context final UriInfo uriInfo) { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); @@ -336,13 +335,23 @@ public class LoansApiResource { LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId); if (loanBasicDetails.isInterestRecalculationEnabled()) { - Collection interestRecalculationCalendarDatas = this.calendarReadPlatformService.retrieveCalendarsByEntity( - loanBasicDetails.getInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), null); + Collection interestRecalculationCalendarDatas = this.calendarReadPlatformService + .retrieveCalendarsByEntity(loanBasicDetails.getInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue(), null); CalendarData calendarData = null; if (!CollectionUtils.isEmpty(interestRecalculationCalendarDatas)) { calendarData = interestRecalculationCalendarDatas.iterator().next(); } - loanBasicDetails = LoanAccountData.withInterestRecalculationCalendarData(loanBasicDetails, calendarData); + + Collection interestRecalculationCompoundingCalendarDatas = this.calendarReadPlatformService + .retrieveCalendarsByEntity(loanBasicDetails.getInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue(), null); + CalendarData compoundingCalendarData = null; + if (!CollectionUtils.isEmpty(interestRecalculationCompoundingCalendarDatas)) { + compoundingCalendarData = interestRecalculationCompoundingCalendarDatas.iterator().next(); + } + loanBasicDetails = LoanAccountData.withInterestRecalculationCalendarData(loanBasicDetails, calendarData, + compoundingCalendarData); } Collection loanRepayments = null; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java index e29612ce1..f1b26a19d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java @@ -565,10 +565,10 @@ public class LoanAccountData { interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, - disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingBalance, emiAmountVariations, - memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, - isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, - paidInAdvance); + disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingBalance, + emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, + daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, + createStandingInstructionAtDisbursement, paidInAdvance); } @@ -595,7 +595,7 @@ public class LoanAccountData { acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); - + } public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product, @@ -862,10 +862,10 @@ public class LoanAccountData { amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, - disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmont, outstandingLoanBalance, emiAmountVariations, - memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, - isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, - paidInAdvance); + disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmont, outstandingLoanBalance, + emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, + daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, + createStandingInstructionAtDisbursement, paidInAdvance); } /* @@ -889,10 +889,10 @@ public class LoanAccountData { final PortfolioAccountData linkedAccount, final Collection disbursementDetails, final Collection emiAmountVariations, final Collection overdueCharges, final PaidInAdvanceData paidInAdvance) { - LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; - if (acc.product != null) { - loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes(); - } + LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; + if (acc.product != null) { + loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes(); + } return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, @@ -1014,10 +1014,11 @@ public class LoanAccountData { } - public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData) { + public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData, + final CalendarData compoundingCalendarData) { final LoanInterestRecalculationData interestRecalculationData = LoanInterestRecalculationData.withCalendarData( - acc.interestRecalculationData, calendarData); + acc.interestRecalculationData, calendarData, compoundingCalendarData); return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, @@ -1285,7 +1286,7 @@ public class LoanAccountData { this.originalSchedule = originalSchedule; this.createStandingInstructionAtDisbursement = createStandingInstructionAtDisbursement; this.paidInAdvance = paidInAdvance; - if(this.product != null){ + if (this.product != null) { this.product.setloanProductConfigurableAttributes(product.getloanProductConfigurableAttributes()); } } @@ -1357,10 +1358,10 @@ public class LoanAccountData { return this.product; } - public void setProduct(LoanProductData product){ - this.product = product; + public void setProduct(LoanProductData product) { + this.product = product; } - + public GroupGeneralData groupData() { return this.group; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanInterestRecalculationData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanInterestRecalculationData.java index 46bd704e5..d86e4dc46 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanInterestRecalculationData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanInterestRecalculationData.java @@ -20,11 +20,18 @@ public class LoanInterestRecalculationData { private final EnumOptionData recalculationRestFrequencyType; private final Integer recalculationRestFrequencyInterval; private final LocalDate recalculationRestFrequencyDate; + private final EnumOptionData recalculationCompoundingFrequencyType; + private final Integer recalculationCompoundingFrequencyInterval; + private final LocalDate recalculationCompoundingFrequencyDate; + @SuppressWarnings("unused") + private final CalendarData compoundingCalendarData; public LoanInterestRecalculationData(final Long id, final Long loanId, final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType, final CalendarData calendarData, final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval, - final LocalDate recalculationRestFrequencyDate) { + final LocalDate recalculationRestFrequencyDate, final CalendarData compoundingCalendarData, + final EnumOptionData recalculationCompoundingFrequencyType, final Integer recalculationCompoundingFrequencyInterval, + final LocalDate recalculationCompoundingFrequencyDate) { this.id = id; this.loanId = loanId; this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; @@ -33,14 +40,20 @@ public class LoanInterestRecalculationData { this.recalculationRestFrequencyType = recalculationRestFrequencyType; this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval; this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; + this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType; + this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval; + this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate; + this.compoundingCalendarData = compoundingCalendarData; } public static LoanInterestRecalculationData withCalendarData(final LoanInterestRecalculationData recalculationData, - final CalendarData calendarData) { + final CalendarData calendarData, CalendarData compoundingCalendarData) { return new LoanInterestRecalculationData(recalculationData.id, recalculationData.loanId, recalculationData.interestRecalculationCompoundingType, recalculationData.rescheduleStrategyType, calendarData, recalculationData.recalculationRestFrequencyType, recalculationData.recalculationRestFrequencyInterval, - recalculationData.recalculationRestFrequencyDate); + recalculationData.recalculationRestFrequencyDate, compoundingCalendarData, + recalculationData.recalculationCompoundingFrequencyType, recalculationData.recalculationCompoundingFrequencyInterval, + recalculationData.recalculationCompoundingFrequencyDate); } public Long getId() { 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 index c174f003f..a9ca10365 100755 --- 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 @@ -17,37 +17,37 @@ public class ScheduleGeneratorDTO { final LocalDate calculatedRepaymentsStartingFromDate; final HolidayDetailDTO holidayDetailDTO; final CalendarInstance calendarInstanceForInterestRecalculation; + final CalendarInstance compoundingCalendarInstance; LocalDate recalculateFrom; final Long overdurPenaltyWaitPeriod; - LocalDate lastTransactionDate; public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO, - final CalendarInstance calendarInstanceForInterestRecalculation) { + final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance) { this.loanScheduleFactory = loanScheduleFactory; this.applicationCurrency = applicationCurrency; this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation; + this.compoundingCalendarInstance = compoundingCalendarInstance; 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) { + final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance, + final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod) { this.loanScheduleFactory = loanScheduleFactory; this.applicationCurrency = applicationCurrency; this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation; + this.compoundingCalendarInstance = compoundingCalendarInstance; this.recalculateFrom = recalculateFrom; this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod; - this.lastTransactionDate = lastTransactionDate; this.holidayDetailDTO = holidayDetailDTO; } @@ -84,10 +84,6 @@ public class ScheduleGeneratorDTO { return penaltyWaitPeriod; } - public LocalDate getLastTransactionDate() { - return this.lastTransactionDate; - } - public HolidayDetailDTO getHolidayDetailDTO() { return this.holidayDetailDTO; } @@ -96,8 +92,8 @@ public class ScheduleGeneratorDTO { this.recalculateFrom = recalculateFrom; } - public void setLastTransactionDate(LocalDate lastTransactionDate) { - this.lastTransactionDate = lastTransactionDate; + public CalendarInstance getCompoundingCalendarInstance() { + return this.compoundingCalendarInstance; } } 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 51d094a88..845f9632f 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 @@ -105,6 +105,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; +import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; @@ -557,8 +558,7 @@ public class Loan extends AbstractPersistable { // store Id's of existing loan transactions and existing reversed loan // transactions final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); updateLoanSummaryDerivedFields(); } @@ -569,8 +569,7 @@ public class Loan extends AbstractPersistable { .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } @@ -707,8 +706,7 @@ public class Loan extends AbstractPersistable { if (removed) { charge.setActive(false); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } @@ -727,7 +725,7 @@ public class Loan extends AbstractPersistable { ***/ final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); + getCurrency(), this.repaymentScheduleInstallments, charges()); } updateLoanSummaryDerivedFields(); @@ -785,12 +783,11 @@ public class Loan extends AbstractPersistable { ***/ final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); + getCurrency(), this.repaymentScheduleInstallments, charges()); } else { // reprocess loan schedule based on charge been waived. final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); } updateLoanSummaryDerivedFields(); @@ -938,12 +935,11 @@ public class Loan extends AbstractPersistable { ***/ final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, - getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); + getCurrency(), this.repaymentScheduleInstallments, charges()); } else { // reprocess loan schedule based on charge been waived. final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); } updateLoanSummaryDerivedFields(); @@ -1174,7 +1170,10 @@ public class Loan extends AbstractPersistable { final String localeAsInput = command.locale(); final LocalDate recalculationRestFrequencyDate = command .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); - updateLoanInterestRecalculationSettings(recalculationRestFrequencyDate, command, actualChanges); + final LocalDate recalculationCompoundingFrequencyDate = command + .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName); + updateLoanInterestRecalculationSettings(recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate, command, + actualChanges); final String accountNoParamName = "accountNo"; if (command.isChangeInStringParameterNamed(accountNoParamName, this.accountNumber)) { @@ -1438,20 +1437,28 @@ public class Loan extends AbstractPersistable { /** * Update interest recalculation settings if product configuration changes */ - public void updateLoanInterestRecalculationSettings(final LocalDate recalculationRestFrequencyDate, final JsonCommand command, - final Map actualChanges) { + public void updateLoanInterestRecalculationSettings(final LocalDate recalculationRestFrequencyDate, + final LocalDate recalculationCompoundingFrequencyDate, final JsonCommand command, final Map actualChanges) { if (isInterestRecalculationEnabledForProduct()) { Date restFrequencyDate = null; if (recalculationRestFrequencyDate != null) { restFrequencyDate = recalculationRestFrequencyDate.toDate(); } + + Date compoundingFrequencyDate = null; + if (recalculationCompoundingFrequencyDate != null) { + compoundingFrequencyDate = recalculationCompoundingFrequencyDate.toDate(); + } if (this.loanInterestRecalculationDetails == null) { + actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, true); this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct .getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct .getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct - .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate); + .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct + .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct + .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); this.loanInterestRecalculationDetails.updateLoan(this); } else { @@ -1691,7 +1698,8 @@ public class Loan extends AbstractPersistable { public void loanApplicationSubmittal(final AppUser currentUser, final LoanScheduleModel loanSchedule, final LoanApplicationTerms loanApplicationTerms, final LoanLifecycleStateMachine lifecycleStateMachine, final LocalDate submittedOn, final String externalId, final boolean allowTransactionsOnHoliday, final List holidays, - final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, final LocalDate recalculationRestFrequencyDate) { + final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, final LocalDate recalculationRestFrequencyDate, + final LocalDate recalculationCompoundingFrequencyDate) { updateLoanSchedule(loanSchedule, currentUser); @@ -1767,11 +1775,18 @@ public class Loan extends AbstractPersistable { if (recalculationRestFrequencyDate != null) { restFrequencyDate = recalculationRestFrequencyDate.toDate(); } + + Date compoundingFrequencyDate = null; + if (recalculationCompoundingFrequencyDate != null) { + compoundingFrequencyDate = recalculationCompoundingFrequencyDate.toDate(); + } this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct .getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct .getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct - .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate); + .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct + .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct + .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); this.loanInterestRecalculationDetails.updateLoan(this); } @@ -2220,8 +2235,7 @@ public class Loan extends AbstractPersistable { .determineProcessor(this.transactionProcessingStrategy); final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } @@ -2374,24 +2388,28 @@ public class Loan extends AbstractPersistable { } CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; RecalculationFrequencyType recalculationFrequencyType = null; InterestRecalculationCompoundingMethod compoundingMethod = null; + RecalculationFrequencyType compoundingFrequencyType = null; + LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation(); - recalculationFrequencyType = this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType(); - compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(this.loanProduct.getProductInterestRecalculationDetails() - .getInterestRecalculationCompoundingMethod()); + compoundingCalendarInstance = scheduleGeneratorDTO.getCompoundingCalendarInstance(); + recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType(); + compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); + compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); + rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); } - final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms - .assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(), loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, - dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), - scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), - this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, - this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), - this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), - recalculationFrequencyType, restCalendarInstance, compoundingMethod, - this.loanProduct.preCloseInterestCalculationStrategy()); + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(), + loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), + getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), + this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, + this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), + this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), + recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, + this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod); final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(), scheduleGeneratorDTO.getHolidayDetailDTO()); @@ -2569,8 +2587,7 @@ public class Loan extends AbstractPersistable { this.loanTermVariations.clear(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); updateLoanSummaryDerivedFields(); } @@ -2790,8 +2807,7 @@ public class Loan extends AbstractPersistable { } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } @@ -3110,7 +3126,7 @@ public class Loan extends AbstractPersistable { } ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, - charges(), getLastUserTransactionForChargeCalc()); + charges()); updateLoanSummaryDerivedFields(); return changedTransactionDetail; } @@ -3249,8 +3265,7 @@ public class Loan extends AbstractPersistable { } final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), - getLastUserTransactionForChargeCalc()); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); this.loanTransactions.add(mapEntry.getValue()); @@ -4527,7 +4542,7 @@ public class Loan extends AbstractPersistable { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, - charges(), getLastUserTransactionForChargeCalc()); + charges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); this.loanTransactions.add(mapEntry.getValue()); @@ -4631,7 +4646,7 @@ public class Loan extends AbstractPersistable { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, - charges(), getLastUserTransactionForChargeCalc()); + charges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } @@ -4652,7 +4667,6 @@ public class Loan extends AbstractPersistable { public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) { LocalDate lastTransactionDate = getLastUserTransactionDate(); - generatorDTO.setLastTransactionDate(lastTransactionDate); final LoanScheduleModel loanSchedule = getRecalculatedSchedule(generatorDTO); if (loanSchedule == null) { return; } updateLoanSchedule(loanSchedule, currentUser); @@ -4681,7 +4695,7 @@ public class Loan extends AbstractPersistable { copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); } loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(getDisbursementDate(), copyTransactions, - getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc()); + getCurrency(), this.repaymentScheduleInstallments, charges()); updateLoanSummaryDerivedFields(); } @@ -4696,19 +4710,21 @@ public class Loan extends AbstractPersistable { final MathContext mc = new MathContext(8, roundingMode); final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO.getApplicationCurrency(), - generatorDTO.getCalculatedRepaymentsStartingFromDate(), generatorDTO.getCalendarInstanceForInterestRecalculation()); + generatorDTO.getCalculatedRepaymentsStartingFromDate(), generatorDTO.getCalendarInstanceForInterestRecalculation(), + generatorDTO.getCompoundingCalendarInstance()); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(), generatorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(), - loanRepaymentScheduleTransactionProcessor, generatorDTO.getLastTransactionDate()); + loanRepaymentScheduleTransactionProcessor); return loanSchedule; } public LoanApplicationTerms constructLoanApplicationTerms(final ApplicationCurrency applicationCurrency, - final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) { + final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation, + final CalendarInstance compoundingCalendarInstance) { final Integer loanTermFrequency = this.termFrequency; final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); final List disbursementData = new ArrayList<>(); @@ -4732,20 +4748,22 @@ public class Loan extends AbstractPersistable { } RecalculationFrequencyType recalculationFrequencyType = this.loanInterestRecalculationDetails().getRestFrequencyType(); + RecalculationFrequencyType compoundingFrequencyType = this.loanInterestRecalculationDetails().getCompoundingFrequencyType(); final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails, - calendarInstanceForInterestRecalculation, recalculationFrequencyType, - this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), - this.loanProduct.preCloseInterestCalculationStrategy()); + calendarInstanceForInterestRecalculation, recalculationFrequencyType, compoundingCalendarInstance, + compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(), + this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy()); return loanApplicationTerms; } public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory, - CalendarInstance restCalendarInstance, HolidayDetailDTO holidayDetailDTO, LocalDate onDate) { + final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, + final HolidayDetailDTO holidayDetailDTO, final LocalDate onDate) { LoanRepaymentScheduleInstallment installment = null; if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { @@ -4783,10 +4801,13 @@ public class Loan extends AbstractPersistable { RecalculationFrequencyType recalculationFrequencyType = null; InterestRecalculationCompoundingMethod compoundingMethod = null; + RecalculationFrequencyType compoundingFrequencyType = null; + LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - recalculationFrequencyType = this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType(); - compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(this.loanProduct - .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod()); + recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType(); + compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); + compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); + rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); } final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(null, loanTermFrequency, @@ -4794,11 +4815,13 @@ public class Loan extends AbstractPersistable { getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), - recalculationFrequencyType, restCalendarInstance, compoundingMethod, - this.loanProduct.preCloseInterestCalculationStrategy()); - - installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), - onDate, loanApplicationTerms, mc, charges(), holidayDetailDTO); + recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, + compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod); + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory + .determineProcessor(this.transactionProcessingStrategy); + installment = loanScheduleGenerator.calculatePrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), onDate, + loanApplicationTerms, mc, charges(), holidayDetailDTO, retreiveListOfTransactionsPostDisbursementExcludeAccruals(), + loanRepaymentScheduleTransactionProcessor); } else { installment = this.getTotalOutstandingOnLoan(); } @@ -4937,13 +4960,15 @@ public class Loan extends AbstractPersistable { /** * @param restCalendarInstance * TODO + * @param compoundingCalendarInstance + * TODO * @param loan * the Loan object * @return application terms of the Loan object **/ @SuppressWarnings({ "unused" }) public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency, - final CalendarInstance restCalendarInstance) { + final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance) { LoanProduct loanProduct = loanProduct(); // LoanProductRelatedDetail loanProductRelatedDetail = // getLoanRepaymentScheduleDetail(); @@ -4998,20 +5023,22 @@ public class Loan extends AbstractPersistable { RecalculationFrequencyType recalculationFrequencyType = null; InterestRecalculationCompoundingMethod compoundingMethod = null; + RecalculationFrequencyType compoundingFrequencyType = null; + LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - recalculationFrequencyType = this.loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType(); - compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(this.loanProduct.getProductInterestRecalculationDetails() - .getInterestRecalculationCompoundingMethod()); + recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType(); + compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); + compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); + rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); } - return LoanApplicationTerms - .assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, - expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, - inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, - disbursementData, maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate, - this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), - recalculationFrequencyType, restCalendarInstance, compoundingMethod, - this.loanProduct.preCloseInterestCalculationStrategy()); + return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, + dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, + inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, + maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate, + this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), + recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, + this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod); } /** @@ -5204,7 +5231,7 @@ public class Loan extends AbstractPersistable { } else { final List allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), - allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges(), null); + allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); for (final Map.Entry mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } 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 bdde73bb3..3385e118a 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 @@ -165,15 +165,17 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; 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()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final MonetaryCurrency currency = loan.getCurrency(); applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -188,8 +190,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, - recalculateDueDateChargesFrom); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment, @@ -547,12 +549,15 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); return scheduleGeneratorDTO; } 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..f72431145 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; @@ -61,24 +62,39 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable @Column(name = "rest_freqency_date") private Date restFrequencyDate; + @Column(name = "compounding_frequency_type_enum", nullable = true) + private Integer compoundingFrequencyType; + + @Column(name = "compounding_frequency_interval", nullable = true) + private Integer compoundingInterval; + + @Temporal(TemporalType.DATE) + @Column(name = "compounding_freqency_date") + private Date compoundingFrequencyDate; + protected LoanInterestRecalculationDetails() { // Default constructor for jpa repository } private LoanInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod, - final Integer restFrequencyType, final Integer restInterval, final Date restFrequencyDate) { + final Integer restFrequencyType, final Integer restInterval, final Date restFrequencyDate, Integer compoundingFrequencyType, + Integer compoundingInterval, Date compoundingFrequencyDate) { this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; this.rescheduleStrategyMethod = rescheduleStrategyMethod; this.restFrequencyDate = restFrequencyDate; this.restFrequencyType = restFrequencyType; this.restInterval = restInterval; + this.compoundingFrequencyDate = compoundingFrequencyDate; + this.compoundingFrequencyType = compoundingFrequencyType; + this.compoundingInterval = compoundingInterval; } public static LoanInterestRecalculationDetails createFrom(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval, - final Date restFrequencyDate) { + final Date restFrequencyDate, final Integer compoundingFrequencyType, final Integer compoundingInterval, + final Date compoundingFrequencyDate) { return new LoanInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, restFrequencyType, - restInterval, restFrequencyDate); + restInterval, restFrequencyDate, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate); } public void updateLoan(final Loan loan) { @@ -96,7 +112,31 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable actualChanges.put("locale", localeAsInput); final LocalDate newValue = command.localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); - this.restFrequencyDate = newValue.toDate(); + if (newValue == null || getRestFrequencyType().isSameAsRepayment()) { + this.restFrequencyDate = null; + } else { + this.restFrequencyDate = newValue.toDate(); + } + } + + if (command.isChangeInLocalDateParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, + getCompoundingFrequencyLocalDate())) { + final String dateFormatAsInput = command.dateFormat(); + final String localeAsInput = command.locale(); + final String valueAsInput = command + .stringValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName); + actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, valueAsInput); + actualChanges.put("dateFormat", dateFormatAsInput); + actualChanges.put("locale", localeAsInput); + + final LocalDate newValue = command + .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName); + if (newValue == null || !getInterestRecalculationCompoundingMethod().isCompoundingEnabled() + || getCompoundingFrequencyType().isSameAsRepayment()) { + this.compoundingFrequencyDate = null; + } else { + this.compoundingFrequencyDate = newValue.toDate(); + } } } @@ -123,4 +163,20 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable public Integer getRestInterval() { return this.restInterval; } + + public LocalDate getCompoundingFrequencyLocalDate() { + LocalDate recurrenceOnLocalDate = null; + if (this.compoundingFrequencyDate != null) { + recurrenceOnLocalDate = new LocalDate(this.compoundingFrequencyDate); + } + return recurrenceOnLocalDate; + } + + public RecalculationFrequencyType getCompoundingFrequencyType() { + return RecalculationFrequencyType.fromInt(this.compoundingFrequencyType); + } + + public Integer getCompoundingInterval() { + return this.compoundingInterval; + } } 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 d9d9ec961..6d4b6f573 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,8 +20,7 @@ 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 LocalDate lastTransactionDate) { + final List repaymentPeriods, final Set loanCharges) { Money totalInterest = Money.zero(currency); Money totalPrincipal = Money.zero(currency); @@ -33,16 +32,14 @@ public class LoanRepaymentScheduleProcessingWrapper { for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, - currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, true, - lastTransactionDate); + currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, true); final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, currency, true); final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), loanCharges, currency, true); final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), - loanCharges, currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, - true, lastTransactionDate); + loanCharges, currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, true); final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, currency, true); final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, @@ -58,8 +55,7 @@ public class LoanRepaymentScheduleProcessingWrapper { private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, int numberOfRepayments, - final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, - final LocalDate lastTransactionDate) { + final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(monetaryCurrency); @@ -86,8 +82,7 @@ public class LoanRepaymentScheduleProcessingWrapper { && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) - && loanCharge.getChargeCalculation().isPercentageBased() - && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { + && loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); @@ -151,8 +146,7 @@ public class LoanRepaymentScheduleProcessingWrapper { private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, - int numberOfRepayments, final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable, - final LocalDate lastTransactionDate) { + int numberOfRepayments, final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(currency); @@ -179,8 +173,7 @@ public class LoanRepaymentScheduleProcessingWrapper { && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) - && loanCharge.getChargeCalculation().isPercentageBased() - && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { + && loanCharge.getChargeCalculation().isPercentageBased()) { BigDecimal amount = BigDecimal.ZERO; if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java index ef9ee710f..63c448d1b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanSummary.java @@ -207,10 +207,8 @@ public final class LoanSummary { this.totalInterestWaived = summaryWrapper.calculateTotalInterestWaived(repaymentScheduleInstallments, currency).getAmount(); this.totalInterestWrittenOff = summaryWrapper.calculateTotalInterestWrittenOff(repaymentScheduleInstallments, currency).getAmount(); - if (totalInterestCharged.isGreaterThanZero()) { - this.totalInterestOutstanding = totalInterestCharged.minus(this.totalInterestRepaid).minus(this.totalInterestWaived) - .minus(this.totalInterestWrittenOff).getAmount(); - } + this.totalInterestOutstanding = totalInterestCharged.minus(this.totalInterestRepaid).minus(this.totalInterestWaived) + .minus(this.totalInterestWrittenOff).getAmount(); final Money totalFeeChargesCharged = summaryWrapper.calculateTotalFeeChargesCharged(repaymentScheduleInstallments, currency).plus( this.totalFeeChargesDueAtDisbursement); @@ -226,10 +224,8 @@ public final class LoanSummary { this.totalFeeChargesWrittenOff = summaryWrapper.calculateTotalFeeChargesWrittenOff(repaymentScheduleInstallments, currency) .getAmount(); - if (totalFeeChargesCharged.isGreaterThanZero()) { - this.totalFeeChargesOutstanding = totalFeeChargesCharged.minus(this.totalFeeChargesRepaid).minus(this.totalFeeChargesWaived) - .minus(this.totalFeeChargesWrittenOff).getAmount(); - } + this.totalFeeChargesOutstanding = totalFeeChargesCharged.minus(this.totalFeeChargesRepaid).minus(this.totalFeeChargesWaived) + .minus(this.totalFeeChargesWrittenOff).getAmount(); final Money totalPenaltyChargesCharged = summaryWrapper .calculateTotalPenaltyChargesCharged(repaymentScheduleInstallments, currency); @@ -241,10 +237,8 @@ public final class LoanSummary { this.totalPenaltyChargesWrittenOff = summaryWrapper.calculateTotalPenaltyChargesWrittenOff(repaymentScheduleInstallments, currency) .getAmount(); - if (totalPenaltyChargesCharged.isGreaterThanZero()) { - this.totalPenaltyChargesOutstanding = totalPenaltyChargesCharged.minus(this.totalPenaltyChargesRepaid) - .minus(this.totalPenaltyChargesWaived).minus(this.totalPenaltyChargesWrittenOff).getAmount(); - } + this.totalPenaltyChargesOutstanding = totalPenaltyChargesCharged.minus(this.totalPenaltyChargesRepaid) + .minus(this.totalPenaltyChargesWaived).minus(this.totalPenaltyChargesWrittenOff).getAmount(); final Money totalExpectedRepayment = Money.of(currency, this.totalPrincipalDisbursed).plus(this.totalInterestCharged) .plus(this.totalFeeChargesCharged).plus(this.totalPenaltyChargesCharged); @@ -286,48 +280,42 @@ public final class LoanSummary { public BigDecimal getTotalWrittenOff() { return this.totalWrittenOff; } - - /** - * @return total interest repaid + + /** + * @return total interest repaid **/ public BigDecimal getTotalInterestRepaid() { - return this.totalInterestRepaid; + return this.totalInterestRepaid; } - + public BigDecimal getTotalFeeChargesCharged() { - return this.totalFeeChargesCharged; + return this.totalFeeChargesCharged; } - + public BigDecimal getTotalPenaltyChargesCharged() { - return this.totalPenaltyChargesCharged; + return this.totalPenaltyChargesCharged; } - public BigDecimal getTotalPrincipalWrittenOff() { return this.totalPrincipalWrittenOff; } - public BigDecimal getTotalInterestWaived() { return this.totalInterestWaived; } - public BigDecimal getTotalFeeChargesRepaid() { return this.totalFeeChargesRepaid; } - public BigDecimal getTotalFeeChargesWaived() { return this.totalFeeChargesWaived; } - public BigDecimal getTotalPenaltyChargesRepaid() { return this.totalPenaltyChargesRepaid; } - public BigDecimal getTotalPenaltyChargesWaived() { return this.totalPenaltyChargesWaived; } 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 003b7d3c5..305bcf2f0 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 @@ -50,16 +50,14 @@ 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 LocalDate recalculateChargesFrom) { + final List installments, final Set charges) { final boolean reprocessCharges = true; - return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom, - reprocessCharges); + return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, reprocessCharges); } private ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate, final List transactionsPostDisbursement, final MonetaryCurrency currency, - final List installments, final Set charges, - final LocalDate recalculateChargesFrom, boolean reprocessCharges) { + final List installments, final Set charges, boolean reprocessCharges) { if (charges != null) { for (final LoanCharge loanCharge : charges) { @@ -78,7 +76,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen // loan charges) if (reprocessCharges) { final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); - wrapper.reprocess(currency, disbursementDate, installments, charges, recalculateChargesFrom); + wrapper.reprocess(currency, disbursementDate, installments, charges); } final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail(); @@ -473,11 +471,10 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen Money unProcessed = Money.zero(currency); for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { Money amountToProcess = null; - final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); - if (newLoanTransaction.isRepayment() || newLoanTransaction.isInterestWaiver() || newLoanTransaction.isRecoveryRepayment()) { - newLoanTransaction.resetDerivedComponents(); + if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { + loanTransaction.resetDerivedComponents(); } - unProcessed = processTransaction(newLoanTransaction, currency, installments, amountToProcess); + unProcessed = processTransaction(loanTransaction, currency, installments, amountToProcess); } return unProcessed; } @@ -571,10 +568,9 @@ 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 List installments, final Set charges) { final boolean reprocessCharges = false; - return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom, - reprocessCharges); + return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, reprocessCharges); } private LoanCharge findLatestPaidChargeFromUnOrderedSet(final Set charges, MonetaryCurrency currency) { 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 cedaa90f4..44c619695 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 @@ -22,8 +22,7 @@ public interface LoanRepaymentScheduleTransactionProcessor { Set charges); ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List repaymentsOrWaivers, - MonetaryCurrency currency, List repaymentScheduleInstallments, Set charges, - LocalDate recalculateChargesFrom); + MonetaryCurrency currency, List repaymentScheduleInstallments, Set charges); void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, List repaymentScheduleInstallments); @@ -39,7 +38,7 @@ public interface LoanRepaymentScheduleTransactionProcessor { ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(LocalDate disbursementDate, List transactionsPostDisbursement, MonetaryCurrency currency, - List installments, Set charges, LocalDate recalculateChargesFrom); + List installments, Set charges); void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List installments, final Set charges); 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 972d3f07e..a65582488 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 @@ -53,14 +53,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Set loanCharges, final HolidayDetailDTO holidayDetailDTO) { final Collection diffAmt = null; final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null; - final LocalDate lastTransactionDate = loanApplicationTerms.getExpectedDisbursementDate(); - return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, lastTransactionDate, - loanRepaymentScheduleTransactionProcessor); + final LocalDate scheduleTillDate = null; + return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, loanRepaymentScheduleTransactionProcessor, + scheduleTillDate); } private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final Collection transactions, - final LocalDate lastTransactionDate, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) { final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); // 1. generate list of proposed schedule due dates @@ -138,13 +138,24 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // from when these amounts will effect the outstanding balance for // interest calculation final Map principalPortionMap = new HashMap<>(); - // compounding(interest and fee) amounts will be added along with + // compounding(principal) amounts will be added along with // date(after applying compounding frequency) // from when these amounts will effect the outstanding balance for // interest calculation final Map latePaymentMap = new HashMap<>(); final List installments = new ArrayList<>(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); + LocalDate lastRestDate = currentDate; + if (loanApplicationTerms.getRestCalendarInstance() != null) { + lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); + } + // compounding(interest/Fee) amounts will be added along with + // date(after applying compounding frequency) + // from when these amounts will effect the outstanding balance for + // interest calculation + final TreeMap compoundingMap = new TreeMap<>(); + final Map> compoundingDateVariations = new HashMap<>(); + boolean isNextRepaymentAvailable = true; while (!outstandingBalance.isZero() || !disburseDetailMap.isEmpty()) { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, @@ -153,11 +164,22 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO); + if (!latePaymentMap.isEmpty()) { + populateCompoundingDatesInPeriod(periodStartDate, scheduledDueDate, currentDate, loanApplicationTerms, holidayDetailDTO, + compoundingMap, loanCharges, currency); + compoundingDateVariations.put(periodStartDate, new TreeMap<>(compoundingMap)); + } + // calculated interest start date for the period periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDate, idealDisbursementDate, periodStartDateApplicableForInterest); int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDateApplicableForInterest, scheduledDueDate).getDays(); + if (scheduleTillDate != null && !scheduledDueDate.isBefore(scheduleTillDate)) { + scheduledDueDate = scheduleTillDate; + isNextRepaymentAvailable = false; + } + // populates the collection with transactions till the due date of // the period for interest recalculation enabled loans Collection applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms, @@ -213,6 +235,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (detail.isProcessed()) { continue; } + boolean updateLatePaymentMap = false; if (detail.getTransactionDate().isBefore(scheduledDueDate)) { if (loanRepaymentScheduleTransactionProcessor != null && loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { @@ -230,18 +253,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (daysInPeriodApplicable > 0) { // 5 determine interest till the transaction // date + if (!compoundingDateVariations.containsKey(periodStartDateApplicableForInterest)) { + compoundingDateVariations.put(periodStartDateApplicableForInterest, new TreeMap<>(compoundingMap)); + } PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal.minus(reducePrincipal), totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, - mergeLateAndPaymentMaps(principalPortionMap, latePaymentMap, disburseDetailMap), - periodStartDateApplicableForInterest, detail.getTransactionDate(), + mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), + compoundingMap, periodStartDateApplicableForInterest, detail.getTransactionDate(), daysInPeriodApplicableForInterest); interestForThisinstallment = principalInterestForThisPeriod.interest(); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); - } Money principalForThisPeriod = principalDisbursed.zero(); @@ -253,10 +278,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener interestForThisinstallment, null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, detail.getTransactionDate(), loanCharges, currency, principalInterest, principalDisbursed, - totalCumulativeInterest, numberOfRepayments, true, lastTransactionDate); + totalCumulativeInterest, numberOfRepayments, true); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate, detail.getTransactionDate(), loanCharges, currency, principalInterest, principalDisbursed, - totalCumulativeInterest, numberOfRepayments, true, lastTransactionDate); + totalCumulativeInterest, numberOfRepayments, true); // 8. sum up real totalInstallmentDue from // components @@ -285,6 +310,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periodStartDate = detail.getTransactionDate(); periodStartDateApplicableForInterest = periodStartDate; + updateLatePaymentMap = true; instalmentNumber++; // creates and insert Loan repayment schedule // for @@ -332,11 +358,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // identify late payments and add compounding // details to // map for interest calculation - if (!reducePrincipal.isZero() || !installment.periodDueDate().isBefore(currentDate)) { - latePaymentMap.clear(); - } else { + updateLatePaidAmountsToPrincipalMap(principalPortionMap, detail.getTransaction(), latePaymentMap, + compoundingMap, loanApplicationTerms, currency, holidayDetailDTO, lastRestDate); + compoundingDateVariations.put(periodStartDateApplicableForInterest, new TreeMap<>(compoundingMap)); + if (updateLatePaymentMap) { updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, scheduledDueDate, - installments, true); + installments, true, lastRestDate, compoundingMap); } } else if (loanRepaymentScheduleTransactionProcessor != null) { LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), @@ -345,7 +372,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener List currentTransactions = createCurrentTransactionList(detail); Money unprocessed = loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, installments); - Money arrears = detail.getTransaction().getAmount(currency); + Money arrears = fetchCompoundedArrears(loanApplicationTerms, currency, detail.getTransaction()); if (unprocessed.isGreaterThanZero()) { arrears = getTotalAmount(latePaymentMap, currency); updateMapWithAmount(principalPortionMap, unprocessed, applicableDate); @@ -359,13 +386,24 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .calculateTillRestFrequencyEnabled()) { LocalDate calculateTill = detail.getTransactionDate(); + if (!compoundingDateVariations.containsKey(periodStartDateApplicableForInterest)) { + compoundingDateVariations.put(periodStartDateApplicableForInterest, new TreeMap<>( + compoundingMap)); + } PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, - totalCumulativePrincipal.minus(reducePrincipal), totalCumulativeInterest, - totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, - outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, - mergeLateAndPaymentMaps(principalPortionMap, latePaymentMap, disburseDetailMap), - periodStartDateApplicableForInterest, calculateTill, daysInPeriodApplicableForInterest); + this.paymentPeriodsInOneYearCalculator, + interestCalculationGraceOnRepaymentPeriodFraction, + totalCumulativePrincipal.minus(reducePrincipal), + totalCumulativeInterest, + totalInterestChargedForFullLoanTerm, + totalOutstandingInterestPaymentDueToGrace, + outstandingBalanceAsPerRest, + loanApplicationTerms, + periodNumber, + mc, + mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), + compoundingMap, periodStartDateApplicableForInterest, calculateTill, + daysInPeriodApplicableForInterest); if (!principalInterestForThisPeriod.interest() .plus(principalInterestForThisPeriod.interestPaymentDueToGrace()).plus(outstandingBalance) .isGreaterThan(unprocessed)) { @@ -398,9 +436,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } - if (arrears.isGreaterThanZero() && applicableDate.isBefore(currentDate)) { - updateMapWithAmount(latePaymentMap, arrears.negated(), applicableDate); - updateMapWithAmount(latePaymentMap, arrears, currentDate); + LocalDate tillDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, + holidayDetailDTO); + if (arrears.isGreaterThanZero() && applicableDate.isBefore(tillDate)) { + updateLatePaidAmountsToPrincipalMap(principalPortionMap, detail.getTransaction(), latePaymentMap, + compoundingMap, loanApplicationTerms, currency, holidayDetailDTO, lastRestDate); + compoundingDateVariations.put(periodStartDateApplicableForInterest, new TreeMap<>(compoundingMap)); } } @@ -418,13 +459,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDate, idealDisbursementDate, periodStartDateApplicableForInterest); + // backup for pre-close transaction + if (compoundingDateVariations.containsKey(periodStartDateApplicableForInterest)) { + compoundingMap.clear(); + compoundingMap.putAll(compoundingDateVariations.get(periodStartDateApplicableForInterest)); + } else { + compoundingDateVariations.put(periodStartDateApplicableForInterest, new TreeMap<>(compoundingMap)); + } // 5 determine principal,interest of repayment period PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal.minus(reducePrincipal), totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, - mergeLateAndPaymentMaps(principalPortionMap, latePaymentMap, disburseDetailMap), periodStartDateApplicableForInterest, - scheduledDueDate, daysInPeriodApplicableForInterest); + mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap, + periodStartDateApplicableForInterest, scheduledDueDate, daysInPeriodApplicableForInterest); if (loanApplicationTerms.getFixedEmiAmount() != null && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) { @@ -466,7 +514,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // 'principalDue' outstandingBalance = outstandingBalance.minus(principalForThisPeriod.minus(reducedBalance)); - if (outstandingBalance.isLessThanZero()) { + if (outstandingBalance.isLessThanZero() || !isNextRepaymentAvailable) { principalForThisPeriod = principalForThisPeriod.plus(outstandingBalance); outstandingBalance = outstandingBalance.zero(); } @@ -474,9 +522,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // applies charges for the period PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod, interestForThisinstallment, null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, currency, - principalInterest, principalDisbursed, totalCumulativeInterest, numberOfRepayments, true, lastTransactionDate); + principalInterest, principalDisbursed, totalCumulativeInterest, numberOfRepayments, true); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate, scheduledDueDate, loanCharges, - currency, principalInterest, principalDisbursed, totalCumulativeInterest, numberOfRepayments, true, lastTransactionDate); + currency, principalInterest, principalDisbursed, totalCumulativeInterest, numberOfRepayments, true); totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); @@ -499,6 +547,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // apply loan transactions on installments to identify early/late // payments for interest recalculation if (isInterestRecalculationRequired(loanApplicationTerms, transactions) && loanRepaymentScheduleTransactionProcessor != null) { + Money principalProcessed = Money.zero(currency); addLoanRepaymentScheduleInstallment(installments, installment); for (RecalculationDetail detail : applicableTransactions) { if (!detail.isProcessed()) { @@ -520,12 +569,18 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener calculateTill = getNextRestScheduleDate(calculateTill.minusDays(1), loanApplicationTerms, holidayDetailDTO); } + if (compoundingDateVariations.containsKey(periodStartDateApplicableForInterest)) { + compoundingMap.clear(); + compoundingMap.putAll(compoundingDateVariations.get(periodStartDateApplicableForInterest)); + } PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, - periodNumber, mc, mergeLateAndPaymentMaps(principalPortionMap, latePaymentMap, disburseDetailMap), - periodStartDateApplicableForInterest, calculateTill, daysInPeriodApplicableForInterest); + periodNumber, mc, + mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), + compoundingMap, periodStartDateApplicableForInterest, calculateTill, + daysInPeriodApplicableForInterest); Money diff = interestForThisinstallment.minus(interestTillDate.interest()); if (!outstandingBalance.minus(diff).isGreaterThanZero()) { outstandingBalance = outstandingBalance.minus(diff); @@ -557,19 +612,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener reducePrincipal = reducePrincipal.plus(unprocessed); reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal); - + principalForThisPeriod = principalForThisPeriod.plus(unprocessed.plus(addToPrinciapal)); + principalProcessed = principalProcessed.plus(unprocessed.plus(addToPrinciapal)); } } - - } - - // identify late payments and adds compounding details to map - if (!reducePrincipal.isZero() || !installment.periodDueDate().isBefore(currentDate)) { - latePaymentMap.clear(); - } else { - updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, scheduledDueDate, - installments, true); } + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, scheduledDueDate, installments, + true, lastRestDate, compoundingMap); + principalForThisPeriod = principalForThisPeriod.minus(principalProcessed); } periods.add(installment); @@ -598,6 +648,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periodStartDateApplicableForInterest = periodStartDate; instalmentNumber++; periodNumber++; + compoundingDateVariations.clear(); } // 7. determine fees and penalties for charges which depends on total @@ -608,12 +659,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money.of(currency, loanScheduleModelPeriod.interestDue()), null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, principalDisbursed, - totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(), - lastTransactionDate); + totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent()); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest, principalDisbursed, - totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(), - lastTransactionDate); + totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent()); totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); totalRepaymentExpected = totalRepaymentExpected.add(feeChargesForInstallment.getAmount()).add( @@ -625,11 +674,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // this block is to add extra re-payment schedules with interest portion // if the loan not paid with in loan term + if (scheduleTillDate != null) { + currentDate = scheduleTillDate; + } if (isInterestRecalculationRequired(loanApplicationTerms, transactions) && latePaymentMap.size() > 0 && currentDate.isAfter(periodStartDate)) { Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentdate(mc, loanApplicationTerms, holidayDetailDTO, currency, periods, periodStartDate, actualRepaymentDate, instalmentNumber, latePaymentMap, currentDate, - loanRepaymentScheduleTransactionProcessor, transactions, installments); + loanRepaymentScheduleTransactionProcessor, principalPortionMap, compoundingMap, transactions, installments, loanCharges); totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest); } @@ -640,6 +692,19 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding); } + private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, + final LoanTransaction transaction) { + Money arrears = transaction.getPrincipalPortion(currency); + if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { + arrears = arrears.plus(transaction.getInterestPortion(currency)); + } + + if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled()) { + arrears = arrears.plus(transaction.getFeeChargesPortion(currency)).plus(transaction.getPenaltyChargesPortion(currency)); + } + return arrears; + } + private boolean isInterestRecalculationRequired(final LoanApplicationTerms loanApplicationTerms, Collection transactions) { return loanApplicationTerms.isInterestRecalculationEnabled() && transactions != null; @@ -650,21 +715,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * (if compounding is enabled) till current date and adds new repayment * schedule detail * + * @param compoundingMap + * TODO + * @param loanCharges + * TODO + * @param principalPortioMap + * TODO + * */ private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final Collection periods, LocalDate periodStartDate, LocalDate actualRepaymentDate, int instalmentNumber, Map latePaymentMap, - LocalDate currentDate, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - final Collection transactions, List installments) { + final LocalDate currentDate, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, + final Map principalPortionMap, TreeMap compoundingMap, + final Collection transactions, final List installments, + Set loanCharges) { boolean isFirstRepayment = false; LocalDate startDate = periodStartDate; - updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, currentDate, installments, false); Money outstanding = Money.zero(currency); Money totalInterest = Money.zero(currency); Money totalCumulativeInterest = Money.zero(currency); - Map retainEntries = new HashMap<>(); + Map disburseDetailsMap = new HashMap<>(); double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); int periodNumberTemp = 1; + LocalDate lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); do { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, @@ -673,11 +747,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (actualRepaymentDate.isAfter(currentDate)) { actualRepaymentDate = currentDate; } - outstanding = updateOutstandingFromLatePayment(periodStartDate, latePaymentMap, outstanding, retainEntries); + outstanding = updateOutstandingFromLatePayment(periodStartDate, latePaymentMap, outstanding); Collection applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms, actualRepaymentDate, transactions); + if (!latePaymentMap.isEmpty()) { + populateCompoundingDatesInPeriod(periodStartDate, actualRepaymentDate, currentDate, loanApplicationTerms, holidayDetailDTO, + compoundingMap, loanCharges, currency); + } + for (RecalculationDetail detail : applicableTransactions) { if (detail.isProcessed()) { continue; @@ -688,8 +767,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, - loanApplicationTerms, periodNumberTemp, mc, latePaymentMap, periodStartDate, detail.getTransactionDate(), - daysInPeriod); + loanApplicationTerms, periodNumberTemp, mc, + mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap, compoundingMap), compoundingMap, + periodStartDate, detail.getTransactionDate(), daysInPeriod); Money interest = principalInterestForThisPeriod.interest(); totalInterest = totalInterest.plus(interest); @@ -705,9 +785,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener startDate = detail.getTransactionDate(); } loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, installments); - updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, currentDate, installments, false); + updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, currentDate, installments, false, + lastRestDate, compoundingMap); outstanding = outstanding.zero(); - outstanding = updateOutstandingFromLatePayment(periodStartDate, latePaymentMap, outstanding, retainEntries); + outstanding = updateOutstandingFromLatePayment(periodStartDate, latePaymentMap, outstanding); + outstanding = updateBalanceForInterestCalculation(principalPortionMap, periodStartDate, outstanding, false); if (latePaymentMap.isEmpty() && !outstanding.isGreaterThanZero()) { break; } @@ -717,7 +799,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, - periodNumberTemp, mc, latePaymentMap, periodStartDate, actualRepaymentDate, daysInPeriod); + periodNumberTemp, mc, + mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap, compoundingMap), compoundingMap, + periodStartDate, actualRepaymentDate, daysInPeriod); Money interest = principalInterestForThisPeriod.interest(); totalInterest = totalInterest.plus(interest); if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { @@ -761,8 +845,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return currentTransactions; } - private Money updateOutstandingFromLatePayment(LocalDate periodStartDate, Map latePaymentMap, Money outstanding, - Map retainEntries) { + private Money updateOutstandingFromLatePayment(LocalDate periodStartDate, Map latePaymentMap, Money outstanding) { + Map retainEntries = new HashMap<>(); for (Map.Entry mapEntry : latePaymentMap.entrySet()) { if (!mapEntry.getKey().isAfter(periodStartDate)) { outstanding = outstanding.plus(mapEntry.getValue()); @@ -832,17 +916,83 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return outstandingBalance; } + // this is to make sure even paid late payments(principal and compounded + // interest/fee) should be reduced as per rest date + private void updateLatePaidAmountsToPrincipalMap(final Map principalVariationMap, + final LoanTransaction loanTransaction, final Map latePaymentsMap, final Map compoundingMap, + final LoanApplicationTerms applicationTerms, final MonetaryCurrency currency, final HolidayDetailDTO holidayDetailDTO, + final LocalDate lastRestDate) { + LocalDate applicableDate = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1), applicationTerms, + holidayDetailDTO); + + Money principalPortion = loanTransaction.getPrincipalPortion(currency); + Money compoundedLatePayments = Money.zero(currency); + if (applicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { + compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getInterestPortion(currency)); + } + if (applicationTerms.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled()) { + compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getFeeChargesPortion(currency)).plus( + loanTransaction.getPenaltyChargesPortion(currency)); + } + + updateCompoundingAmount(principalVariationMap, latePaymentsMap, currency, lastRestDate, principalPortion, applicableDate); + updateCompoundingAmount(principalVariationMap, compoundingMap, currency, lastRestDate, compoundedLatePayments, applicableDate); + } + + private void updateCompoundingAmount(final Map principalVariationMap, + final Map latePaymentCompoundingMap, final MonetaryCurrency currency, final LocalDate lastRestDate, + Money compoundedPortion, final LocalDate applicableDate) { + Money appliedOnPrincipalVariationMap = Money.zero(currency); + Map temp = new HashMap<>(); + for (LocalDate date : latePaymentCompoundingMap.keySet()) { + if (date.isBefore(lastRestDate)) { + Money money = latePaymentCompoundingMap.get(date); + appliedOnPrincipalVariationMap = appliedOnPrincipalVariationMap.plus(money); + if (appliedOnPrincipalVariationMap.isLessThan(compoundedPortion)) { + if (date.isBefore(applicableDate)) { + updateMapWithAmount(principalVariationMap, money.negated(), date); + updateMapWithAmount(principalVariationMap, money, applicableDate); + } + } else if (temp.isEmpty()) { + Money diff = money.minus(appliedOnPrincipalVariationMap.minus(compoundedPortion)); + updateMapWithAmount(principalVariationMap, diff.negated(), date); + updateMapWithAmount(principalVariationMap, diff, applicableDate); + updateMapWithAmount(temp, money.minus(diff), date); + updateMapWithAmount(temp, money.minus(diff).negated(), lastRestDate); + } else { + updateMapWithAmount(temp, money, date); + updateMapWithAmount(temp, money.negated(), lastRestDate); + } + } + } + latePaymentCompoundingMap.clear(); + latePaymentCompoundingMap.putAll(temp); + } + /** * this Method updates late/ not paid installment components to Map with * effective date as per REST(for principal portion ) and compounding * (interest or fee or interest and fee portions) frequency * + * @param lastRestDate + * TODO + * @param compoundingMap + * TODO + * */ private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final Map latePaymentMap, final LocalDate scheduledDueDate, - List installments, boolean applyRestFrequencyForPrincipal) { + List installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate, + final TreeMap compoundingMap) { latePaymentMap.clear(); + LocalDate currentDate = DateUtils.getLocalDateOfTenant(); + Money totalCompoundingAmount = Money.zero(currency); + Money compoundedMoney = Money.zero(currency); + if (!compoundingMap.isEmpty()) { + compoundedMoney = compoundingMap.get(lastRestDate); + } + boolean clearCompoundingMap = true; for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { if (loanRepaymentScheduleInstallment.isNotFullyPaidOff() && !loanRepaymentScheduleInstallment.getDueDate().isAfter(scheduledDueDate) @@ -852,28 +1002,38 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener principalEffectiveDate = getNextRestScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); } - if (principalEffectiveDate.isBefore(DateUtils.getLocalDateOfTenant())) { + if (principalEffectiveDate.isBefore(currentDate)) { updateMapWithAmount(latePaymentMap, loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency), principalEffectiveDate); totalCompoundingAmount = totalCompoundingAmount .plus(loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency)); } - totalCompoundingAmount = updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, - totalCompoundingAmount, loanRepaymentScheduleInstallment); + + final Money changedCompoundedMoney = updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency, + compoundingMap, loanRepaymentScheduleInstallment, lastRestDate, compoundedMoney, scheduledDueDate); + if (compoundedMoney.isZero() || !compoundedMoney.isEqualTo(changedCompoundedMoney)) { + compoundedMoney = changedCompoundedMoney; + clearCompoundingMap = false; + } } } - if (totalCompoundingAmount.isGreaterThanZero()) { - updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), DateUtils.getLocalDateOfTenant()); + updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), lastRestDate); + } + if (clearCompoundingMap) { + compoundingMap.clear(); } } private Money updateMapWithCompoundingDetails(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - final MonetaryCurrency currency, final Map latePaymentMap, Money totalCompoundingAmount, - LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) { + final MonetaryCurrency currency, final TreeMap compoundingMap, + final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final LocalDate lastRestDate, + final Money compoundedMoney, final LocalDate scheduledDueDate) { + Money ignoreMoney = compoundedMoney; if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(loanRepaymentScheduleInstallment.getDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); + if (compoundingEffectiveDate.isBefore(DateUtils.getLocalDateOfTenant())) { Money amount = Money.zero(currency); switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) { @@ -892,11 +1052,61 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener default: break; } - updateMapWithAmount(latePaymentMap, amount, compoundingEffectiveDate); - totalCompoundingAmount = totalCompoundingAmount.plus(amount); + if (compoundingEffectiveDate.isBefore(scheduledDueDate)) { + ignoreMoney = ignoreMoney.plus(amount); + if (ignoreMoney.isGreaterThanZero()) { + updateMapWithAmount(compoundingMap, ignoreMoney, compoundingEffectiveDate); + updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate); + ignoreMoney = ignoreMoney.zero(); + } + } else { + if (ignoreMoney.isLessThanZero()) { + LocalDate firstKey = compoundingMap.firstKey(); + updateMapWithAmount(compoundingMap, ignoreMoney, firstKey); + updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate); + ignoreMoney = ignoreMoney.zero(); + } + updateMapWithAmount(compoundingMap, amount, compoundingEffectiveDate); + updateMapWithAmount(compoundingMap, amount.negated(), lastRestDate); + } } } - return totalCompoundingAmount; + return ignoreMoney; + } + + private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LocalDate currentDate, + final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + final Map compoundingMap, final Set charges, MonetaryCurrency currency) { + if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { + LocalDate lastCompoundingDate = startDate; + LocalDate compoundingDate = startDate; + while (compoundingDate.isBefore(endDate) && compoundingDate.isBefore(currentDate)) { + compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO); + if (!compoundingDate.isBefore(currentDate)) { + break; + } else if (compoundingDate.isAfter(endDate)) { + updateMapWithAmount(compoundingMap, Money.zero(currency), compoundingDate); + } else { + Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, + null, loanApplicationTerms.getPrincipal(), null, loanApplicationTerms.getNumberOfRepayments(), false); + Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate, compoundingDate, charges, + currency, null, loanApplicationTerms.getPrincipal(), null, loanApplicationTerms.getNumberOfRepayments(), false); + updateMapWithAmount(compoundingMap, feeChargesForInstallment.plus(penaltyChargesForInstallment), compoundingDate); + } + lastCompoundingDate = compoundingDate; + } + } + } + + protected void clearMapDetails(final LocalDate startDate, final Map compoundingMap) { + Map temp = new HashMap<>(); + for (LocalDate date : compoundingMap.keySet()) { + if (!date.isBefore(startDate)) { + temp.put(date, compoundingMap.get(date)); + } + } + compoundingMap.clear(); + compoundingMap.putAll(temp); } /** @@ -916,13 +1126,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } /** - * merges all the applicable amounts(disbursements, late payment compounding - * and principal change as per rest) changes to single map for interest - * calculation + * merges all the applicable amounts(compounding dates, disbursements, late + * payment compounding and principal change as per rest) changes to single + * map for interest calculation + * + * @param compoundingDates + * TODO */ - private Map mergeLateAndPaymentMaps(final Map princiaplPaidMap, - final Map latePaymentMap, Map disburseDetailsMap) { - Map map = new TreeMap<>(); + private TreeMap mergeVariationsToMap(final Map princiaplPaidMap, + final Map latePaymentMap, final Map disburseDetailsMap, + final Map compoundingDates) { + TreeMap map = new TreeMap<>(); map.putAll(latePaymentMap); for (Map.Entry mapEntry : disburseDetailsMap.entrySet()) { @@ -940,6 +1154,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } map.put(mapEntry.getKey(), value); } + + for (Map.Entry mapEntry : compoundingDates.entrySet()) { + Money value = mapEntry.getValue(); + if (!map.containsKey(mapEntry.getKey())) { + map.put(mapEntry.getKey(), value.zero()); + } + } + return map; } @@ -979,7 +1201,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener @Override public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, - final CalendarInstance restCalendarInstance) { + final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance) { final Loan loan = loanRescheduleRequest.getLoan(); final LoanSummary loanSummary = loan.getSummary(); @@ -1120,7 +1342,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } // get the loan application terms from the Loan object - final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance); + final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance, + compoundingCalendarInstance); // update the number of repayments loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); @@ -1194,7 +1417,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext, - null, installmentFromDate, adjustedInstallmentDueDate, daysInInstallment); + null, null, installmentFromDate, adjustedInstallmentDueDate, daysInInstallment); // update the interest due for the period period.updateInterestDue(principalInterestForThisPeriod.interest()); @@ -1212,7 +1435,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance, - loanApplicationTerms, periodNumber, mathContext, null, installmentFromDate, adjustedInstallmentDueDate, + loanApplicationTerms, periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate, daysInInstallment); period.updatePrincipalDue(principalInterestForThisPeriod.principal()); @@ -1279,8 +1502,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator, double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, - LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, Map principalVariation, - LocalDate periodStartDate, LocalDate periodEndDate, int daysForInterestInFullPeriod); + LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap principalVariation, + Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, int daysForInterestInFullPeriod); protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) { return periodNumber == numberOfRepayments; @@ -1347,7 +1570,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, final LocalDate lastTransactionDate) { + boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(monetaryCurrency); @@ -1360,8 +1583,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) - && loanCharge.getChargeCalculation().isPercentageBased() - && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { + && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, cumulative, loanCharge); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { @@ -1411,8 +1633,7 @@ 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 LocalDate lastTransactionDate) { + final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, boolean isInstallmentChargeApplicable) { Money cumulative = Money.zero(monetaryCurrency); @@ -1425,8 +1646,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = cumulative.plus(loanCharge.chargeAmount()); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) - && loanCharge.getChargeCalculation().isPercentageBased() - && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) { + && loanCharge.getChargeCalculation().isPercentageBased()) { cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, cumulative, loanCharge); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { @@ -1446,14 +1666,24 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener @Override public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final HolidayDetailDTO holidayDetailDTO, final List transactions, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate lastTransactionDate) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { Collection recalculationDetails = new ArrayList<>(); for (LoanTransaction loanTransaction : transactions) { - recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), loanTransaction)); + recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction + .copyTransactionProperties(loanTransaction))); } - return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails, lastTransactionDate, - loanRepaymentScheduleTransactionProcessor); + final LocalDate scheduleTillDate = null; + return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails, + loanRepaymentScheduleTransactionProcessor, scheduleTillDate); + } + + private List fetchInstallmentsFromScheduleModel(final LoanScheduleModel loanScheduleModel) { + final List installments = new ArrayList<>(); + for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) { + addLoanRepaymentScheduleInstallment(installments, loanScheduleModelPeriod); + } + return installments; } private void addLoanRepaymentScheduleInstallment(final List installments, @@ -1485,11 +1715,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener private LocalDate getNextCompoundScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) { LocalDate nextScheduleDate = null; - if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { + if (!loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { return null; } + if (loanApplicationTerms.getCompoundingFrequencyType().isSameAsRepayment()) { nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate, loanApplicationTerms, holidayDetailDTO); } else { - CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); + CalendarInstance calendarInstance = loanApplicationTerms.getCompoundingCalendarInstance(); nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate); } @@ -1500,150 +1731,35 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * Method returns the amount payable to close the loan account as of today. */ @Override - public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(final List installments, + public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, MonetaryCurrency currency, final LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, - Set charges, final HolidayDetailDTO holidayDetailDTO) { + Set charges, final HolidayDetailDTO holidayDetailDTO, final List loanTransactions, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { + + LocalDate calculateTill = onDate; + if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) { + calculateTill = getNextRestScheduleDate(onDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); + } + Collection recalculationDetails = new ArrayList<>(); + for (LoanTransaction loanTransaction : loanTransactions) { + recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction + .copyTransactionProperties(loanTransaction))); + } + LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, charges, holidayDetailDTO, recalculationDetails, + loanRepaymentScheduleTransactionProcessor, calculateTill); + installments = fetchInstallmentsFromScheduleModel(loanScheduleModel); + loanRepaymentScheduleTransactionProcessor.handleTransaction(loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions, + currency, installments, charges); Money feeCharges = Money.zero(currency); Money penaltyCharges = Money.zero(currency); - Money totalCompoundingAmount = Money.zero(currency); Money totalPrincipal = Money.zero(currency); Money totalInterest = Money.zero(currency); - Money reducePrincipalAsperRest = Money.zero(currency); - LocalDate reducePrincipalRestDate = onDate; - LocalDate calculateInterestFrom = onDate; - LocalDate periodStartDate = onDate; - LocalDate currentDate = DateUtils.getLocalDateOfTenant(); - Money principalOutstanding = Money.zero(currency); - Integer periodNumber = 1; - // for multi disburse loan to identify all disbursed principal for - // interest calculation - Map differenceMap = new TreeMap<>(); - Map disburseAmtMap = new TreeMap<>(); - if (loanApplicationTerms.isMultiDisburseLoan()) { - for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) { - if (disbursementData.isDisbursed()) { - principalOutstanding = principalOutstanding.plus(disbursementData.amount()); - disburseAmtMap.put(disbursementData.disbursementDate(), Money.of(currency, disbursementData.amount())); - } - } - } else { - principalOutstanding = loanApplicationTerms.getPrincipal(); - } - - // adds all the unpaid principal till date and identifies the total - // outstanding for interest calculation for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { if (currentInstallment.isNotFullyPaidOff()) { - if (!currentInstallment.getDueDate().isAfter(onDate)) { - totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency)); - totalInterest = totalInterest.plus(currentInstallment.getInterestOutstanding(currency)); - feeCharges = feeCharges.plus(currentInstallment.getFeeChargesOutstanding(currency)); - penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency)); - principalOutstanding = principalOutstanding.minus(currentInstallment.getPrincipal(currency)); - LocalDate restDate = getNextRestScheduleDate(currentInstallment.getDueDate().minusDays(1), loanApplicationTerms, - holidayDetailDTO); - if (restDate.isAfter(currentDate)) { - reducePrincipalRestDate = restDate; - reducePrincipalAsperRest = reducePrincipalAsperRest.plus(currentInstallment.getPrincipalOutstanding(currency)); - } - - updateMapWithAmount(differenceMap, currentInstallment.getPrincipalOutstanding(currency), - currentInstallment.getDueDate()); - totalCompoundingAmount = updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency, - differenceMap, totalCompoundingAmount, currentInstallment); - } else { - totalPrincipal = totalPrincipal.minus(currentInstallment.getPrincipalCompleted(currency)); - totalInterest = totalInterest.minus(currentInstallment.getInterestPaid(currency)).minus( - currentInstallment.getInterestWaived(currency)); - if (!currentInstallment.getFromDate().isAfter(periodStartDate)) { - if (!currentInstallment.isRecalculatedInterestComponent()) { - periodStartDate = currentInstallment.getFromDate(); - } - calculateInterestFrom = currentInstallment.getFromDate(); - - } - - } - } else { - principalOutstanding = principalOutstanding.minus(currentInstallment.getPrincipal(currency)); - } - } - Money arrears = totalPrincipal; - totalPrincipal = totalPrincipal.plus(principalOutstanding); - - for (Map.Entry disburseDetail : disburseAmtMap.entrySet()) { - if (disburseDetail.getKey().isAfter(periodStartDate)) { - differenceMap.put(disburseDetail.getKey(), disburseDetail.getValue()); - principalOutstanding = principalOutstanding.minus(disburseDetail.getValue()); - } - } - - // interest calculation on outstanding amount till on date from the - // period start of installment where on date falls - Money interest = Money.zero(currency); - LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( - loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, true); - final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate( - loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate); - if (calculateInterestFrom.isBefore(onDate) && !periodStartDate.isBefore(idealDisbursementDate)) { - - boolean isFirstRepayment = false; - if (periodStartDate.isBefore(firstRepaymentdate)) { - isFirstRepayment = true; - } - LocalDate scheduledDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(periodStartDate, loanApplicationTerms, - isFirstRepayment); - isFirstRepayment = false; - - LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDate, - idealDisbursementDate, periodStartDate); - int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDateApplicableForInterest, scheduledDueDate).getDays(); - - if (calculateInterestFrom.isBefore(periodStartDateApplicableForInterest)) { - calculateInterestFrom = periodStartDateApplicableForInterest; - } - LocalDate calculateTill = onDate; - if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) { - LocalDate applicableDate = getNextRestScheduleDate(onDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); - calculateTill = applicableDate; - } - if (arrears.isGreaterThanZero()) { - updateMapWithAmount(differenceMap, arrears.minus(reducePrincipalAsperRest).plus(totalCompoundingAmount).negated(), - currentDate); - updateMapWithAmount(differenceMap, reducePrincipalAsperRest.negated(), reducePrincipalRestDate); - } - if (calculateInterestFrom.isBefore(onDate)) { - double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); - PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( - this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), - totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), principalOutstanding, loanApplicationTerms, - periodNumber, mc, differenceMap, calculateInterestFrom, calculateTill, daysInPeriodApplicableForInterest); - interest = interest.plus(principalInterestForThisPeriod.interest()); - - } - } - - totalInterest = totalInterest.plus(interest); - PrincipalInterest principalInterest = new PrincipalInterest(principalOutstanding, interest, null); - - // apply changes applicable in the period - for (LoanCharge loanCharge : charges) { - if (loanCharge.isActive() && loanCharge.isNotFullyPaid() && !loanCharge.isWaived() - && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStartDate, onDate)) { - if (loanCharge.isFeeCharge()) { - feeCharges = feeCharges.plus(loanCharge.amountOutstanding()); - } else { - penaltyCharges = penaltyCharges.plus(loanCharge.amountOutstanding()); - - } - } else if (loanCharge.isActive() && interest.isGreaterThanZero() && loanCharge.isInstalmentFee()) { - if (loanCharge.isFeeCharge()) { - feeCharges = feeCharges.plus(calculateInstallmentCharge(principalInterest, - loanApplicationTerms.getNumberOfRepayments(), feeCharges.zero(), loanCharge)); - } else { - penaltyCharges = penaltyCharges.plus(calculateInstallmentCharge(principalInterest, - loanApplicationTerms.getNumberOfRepayments(), feeCharges.zero(), loanCharge)); - } + totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency)); + totalInterest = totalInterest.plus(currentInstallment.getInterestOutstanding(currency)); + feeCharges = feeCharges.plus(currentInstallment.getFeeChargesOutstanding(currency)); + penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency)); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java index 74b5e4faf..007c67ac9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java @@ -7,9 +7,11 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.MathContext; import java.util.Map; +import java.util.TreeMap; import org.joda.time.Days; import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; @@ -46,16 +48,27 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS @SuppressWarnings("unused") final Money totalCumulativeInterest, @SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, - final Map principalVariation, final LocalDate periodStartDate, final LocalDate periodEndDate, - final int daysForInterestInFullPeriod) { + final TreeMap principalVariation, final Map compoundingMap, + final LocalDate periodStartDate, final LocalDate periodEndDate, final int daysForInterestInFullPeriod) { LocalDate interestStartDate = periodStartDate; Money interestForThisInstallment = totalCumulativePrincipal.zero(); + Money compoundedMoney = totalCumulativePrincipal.zero(); + Money compoundedInterest = totalCumulativePrincipal.zero(); Money balanceForInterestCalculation = outstandingBalance; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays(); if (principalVariation != null) { + // identifies rest date after current date for reducing all + // compounding + // values + LocalDate compoundingEndDate = principalVariation.ceilingKey(DateUtils.getLocalDateOfTenant()); + if (compoundingEndDate == null) { + compoundingEndDate = DateUtils.getLocalDateOfTenant(); + } + for (Map.Entry principal : principalVariation.entrySet()) { + if (!principal.getKey().isAfter(periodEndDate)) { int interestForDays = Days.daysBetween(interestStartDate, principal.getKey()).getDays(); if (interestForDays > 0) { @@ -71,10 +84,31 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); interestStartDate = principal.getKey(); } - balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()); + Money compoundFee = totalCumulativePrincipal.zero(); + if (compoundingMap.containsKey(principal.getKey())) { + Money interestToBeCompounded = totalCumulativePrincipal.zero(); + // for interest compounding + if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { + interestToBeCompounded = interestForThisInstallment.minus(compoundedInterest); + balanceForInterestCalculation = balanceForInterestCalculation.plus(interestToBeCompounded); + compoundedInterest = interestForThisInstallment; + } + // fee compounding will be done after calculation + compoundFee = compoundingMap.get(principal.getKey()); + compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee); + } + balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()).plus(compoundFee); } } + if (!periodEndDate.isBefore(compoundingEndDate)) { + balanceForInterestCalculation = balanceForInterestCalculation.minus(compoundedMoney); + compoundingMap.clear(); + } else if (compoundedMoney.isGreaterThanZero()) { + compoundingMap.put(periodEndDate, compoundedMoney); + compoundingMap.put(compoundingEndDate, compoundedMoney.negated()); + clearMapDetails(periodEndDate, compoundingMap); + } } int interestForDays = Days.daysBetween(interestStartDate, periodEndDate).getDays(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java index e9a8bec6f..e5a75bf91 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.MathContext; import java.util.Map; +import java.util.TreeMap; import org.joda.time.Days; import org.joda.time.LocalDate; @@ -19,7 +20,8 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, final Money totalCumulativeInterest, final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, - @SuppressWarnings("unused") Map principalVariation, LocalDate periodStartDate, LocalDate periodEndDate, + @SuppressWarnings("unused") TreeMap principalVariation, + @SuppressWarnings("unused") Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, @SuppressWarnings("unused") int daysForInterestInFullPeriod) { final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays(); Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, 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 ef9bc4498..2bc7c940e 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 @@ -135,6 +135,10 @@ public final class LoanApplicationTerms { private final RecalculationFrequencyType recalculationFrequencyType; + private final CalendarInstance compoundingCalendarInstance; + + private final RecalculationFrequencyType compoundingFrequencyType; + private final BigDecimal principalThresholdForLastInstalment; private final Integer installmentAmountInMultiplesOf; @@ -154,6 +158,7 @@ public final class LoanApplicationTerms { final List emiAmountVariations, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled, final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, + final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy) { @@ -166,8 +171,8 @@ public final class LoanApplicationTerms { graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, - recalculationFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - preClosureInterestCalculationStrategy); + recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, + installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -179,8 +184,10 @@ public final class LoanApplicationTerms { final List emiAmountVariations, final LocalDate interestChargedFromDate, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, - final InterestRecalculationCompoundingMethod compoundingMethod, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy) { + final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance, + final RecalculationFrequencyType compoundingFrequencyType, + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, + final LoanRescheduleStrategyMethod rescheduleStrategyMethod) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -203,7 +210,6 @@ public final class LoanApplicationTerms { final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); - final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay.getValue(), dayOfWeek, amortizationMethod, interestMethod, @@ -212,8 +218,9 @@ public final class LoanApplicationTerms { graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, - rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, - principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy); + rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, + compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, + loanPreClosureInterestCalculationStrategy); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -223,8 +230,9 @@ public final class LoanApplicationTerms { final BigDecimal emiAmount, final List disbursementDatas, final BigDecimal maxOutstandingBalance, final List emiAmountVariations, final LocalDate interestChargedFromDate, final LoanInterestRecalculationDetails interestRecalculationDetails, final CalendarInstance restCalendarInstance, - final RecalculationFrequencyType recalculationFrequencyType, final BigDecimal principalThresholdForLastInstalment, - Integer installmentAmountInMultiplesOf, + final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance compoundingCalendarInstance, + final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, + final Integer installmentAmountInMultiplesOf, final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); @@ -262,8 +270,9 @@ public final class LoanApplicationTerms { graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, - interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, - principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy); + interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, + compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, + loanPreClosureInterestCalculationStrategy); } public static LoanApplicationTerms assembleFrom(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, @@ -281,7 +290,8 @@ public final class LoanApplicationTerms { applicationTerms.graceOnArrearsAgeing, applicationTerms.daysInMonthType, applicationTerms.daysInYearType, applicationTerms.interestRecalculationEnabled, applicationTerms.rescheduleStrategyMethod, applicationTerms.interestRecalculationCompoundingMethod, applicationTerms.restCalendarInstance, - applicationTerms.recalculationFrequencyType, applicationTerms.principalThresholdForLastInstalment, + applicationTerms.recalculationFrequencyType, applicationTerms.compoundingCalendarInstance, + applicationTerms.compoundingFrequencyType, applicationTerms.principalThresholdForLastInstalment, applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy); } @@ -300,6 +310,7 @@ public final class LoanApplicationTerms { final boolean isInterestRecalculationEnabled, final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod, final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType, + final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy) { this.currency = currency; @@ -342,7 +353,9 @@ public final class LoanApplicationTerms { this.rescheduleStrategyMethod = rescheduleStrategyMethod; this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; this.restCalendarInstance = restCalendarInstance; + this.compoundingCalendarInstance = compoundingCalendarInstance; this.recalculationFrequencyType = recalculationFrequencyType; + this.compoundingFrequencyType = compoundingFrequencyType; this.principalThresholdForLastInstalment = principalThresholdForLastInstalment; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.preClosureInterestCalculationStrategy = preClosureInterestCalculationStrategy; @@ -1183,4 +1196,12 @@ public final class LoanApplicationTerms { public LoanPreClosureInterestCalculationStrategy getPreClosureInterestCalculationStrategy() { return this.preClosureInterestCalculationStrategy; } + + public CalendarInstance getCompoundingCalendarInstance() { + return this.compoundingCalendarInstance; + } + + public RecalculationFrequencyType getCompoundingFrequencyType() { + return this.compoundingFrequencyType; + } } \ 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 df40340d5..85425d10c 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 @@ -28,13 +28,12 @@ public interface LoanScheduleGenerator { LoanScheduleModel rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set loanCharges, final HolidayDetailDTO holidayDetailDTO, List transactions, - LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - LocalDate lastTransactionDate); + LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, - Set charges, HolidayDetailDTO holidayDetailDTO); + Set charges, HolidayDetailDTO holidayDetailDTO, List loanTransactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance); + final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance); } \ No newline at end of file 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 5af525b58..b01769b4a 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 @@ -67,7 +67,9 @@ import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductInterestRecalculationDetails; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; @@ -256,9 +258,12 @@ public class LoanScheduleAssembler { final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled(); RecalculationFrequencyType recalculationFrequencyType = null; CalendarInstance restCalendarInstance = null; - + RecalculationFrequencyType compoundingFrequencyType = null; + CalendarInstance compoundingCalendarInstance = null; if (isInterestRecalculationEnabled) { - recalculationFrequencyType = loanProduct.getProductInterestRecalculationDetails().getRestFrequencyType(); + LoanProductInterestRecalculationDetails loanProductInterestRecalculationDetails = loanProduct + .getProductInterestRecalculationDetails(); + recalculationFrequencyType = loanProductInterestRecalculationDetails.getRestFrequencyType(); if (!recalculationFrequencyType.isSameAsRepayment()) { LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( LoanProductConstants.recalculationRestFrequencyDateParamName, element); @@ -266,7 +271,22 @@ public class LoanScheduleAssembler { calendarStartDate = expectedDisbursementDate; } restCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, recalculationFrequencyType, - loanProduct.getProductInterestRecalculationDetails().getRestInterval()); + loanProductInterestRecalculationDetails.getRestInterval()); + } + InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod + .fromInt(loanProductInterestRecalculationDetails.getInterestRecalculationCompoundingMethod()); + if (compoundingMethod.isCompoundingEnabled()) { + compoundingFrequencyType = loanProductInterestRecalculationDetails.getCompoundingFrequencyType(); + if (!compoundingFrequencyType.isSameAsRepayment()) { + LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( + LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element); + if (calendarStartDate == null) { + calendarStartDate = expectedDisbursementDate; + } + compoundingCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, compoundingFrequencyType, + loanProductInterestRecalculationDetails.getCompoundingInterval()); + } + } } @@ -281,8 +301,8 @@ public class LoanScheduleAssembler { graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, - restCalendarInstance, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanProduct.preCloseInterestCalculationStrategy()); + restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, + installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy()); } private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, @@ -307,7 +327,7 @@ public class LoanScheduleAssembler { final String title = "loan_recalculation_detail"; final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), calendarFrequencyType, frequency, repeatsOnDay); - return CalendarInstance.from(calendar, null, CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + return CalendarInstance.from(calendar, null, CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); } private List fetchDisbursementData(final JsonObject command) { @@ -434,8 +454,7 @@ public class LoanScheduleAssembler { public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, List transactions, final Set loanCharges, - final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, - LocalDate recalculateDueDateChargesFrom) { + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final MathContext mc = new MathContext(8, roundingMode); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); @@ -447,12 +466,13 @@ public class LoanScheduleAssembler { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions, - loanRepaymentScheduleTransactionProcessor, recalculateDueDateChargesFrom); + loanRepaymentScheduleTransactionProcessor); } public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, final Set loanCharges, - final Long officeId) { + final Long officeId, List loanTransactions, + final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final MathContext mc = new MathContext(8, roundingMode); @@ -464,7 +484,7 @@ public class LoanScheduleAssembler { HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, onDate, loanApplicationTerms, mc, loanCharges, - holidayDetailDTO); + holidayDetailDTO, loanTransactions, loanRepaymentScheduleTransactionProcessor); } 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 73c6b1ed2..e7017cbfe 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 @@ -159,13 +159,16 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); final CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( - loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + final CalendarInstance compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); LocalDate calculatedRepaymentsStartingFromDate = accountDomainService.getCalculatedRepaymentsStartingFromDate( loan.getDisbursementDate(), loan, calendarInstance); LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency, - calculatedRepaymentsStartingFromDate, restCalendarInstance); + calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance); LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount( - loan.fetchRepaymentScheduleInstallments(), currency, today, loanApplicationTerms, loan.charges(), loan.getOfficeId()); + loan.fetchRepaymentScheduleInstallments(), currency, today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), + loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor); Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus( loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); Money interestDue = Money.zero(currency); @@ -186,7 +189,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC } LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(), - modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor, today); + modifiedTransactions, loan.charges(), loanRepaymentScheduleTransactionProcessor); 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 ec819dc09..1fff0131f 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 @@ -19,19 +19,20 @@ public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory { @Override public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, - final HolidayDetailDTO holidayDetailDTO,final CalendarInstance restCalendarInstance) { + final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance, + final CalendarInstance compoundingCalendarInstance) { LoanRescheduleModel loanRescheduleModel = null; switch (interestMethod) { case DECLINING_BALANCE: loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, - applicationCurrency, holidayDetailDTO, restCalendarInstance); + applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); break; case FLAT: loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, - applicationCurrency, holidayDetailDTO, restCalendarInstance); + applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); break; case INVALID: 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 9e06cc66d..dd4d8a051 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 @@ -16,5 +16,5 @@ public interface LoanReschedulerFactory { public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, - final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance); + final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance); } 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 d286ab9b6..f3bb8c58a 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 @@ -85,13 +85,16 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance); + loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); 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 4d1597082..3bd1fe418 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 @@ -322,12 +322,15 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( - loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance); + loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); final Collection periods = loanRescheduleModel.getPeriods(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); @@ -450,6 +453,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; ApplicationCurrency applicationCurrency = null; LocalDate calculatedRepaymentsStartingFromDate = null; List holidays = null; @@ -460,8 +464,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche Long overdurPenaltyWaitPeriod = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( - loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); - + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final MonetaryCurrency currency = loan.getCurrency(); applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), @@ -478,8 +483,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod, lastTransactionDate); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, + recalculateFrom, overdurPenaltyWaitPeriod); Money accruedCharge = Money.zero(loan.getCurrency()); if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 8f0c15ca4..3d00ad803 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -27,6 +27,7 @@ import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; +import org.mifosplatform.portfolio.loanaccount.domain.LoanInterestRecalculationDetails; import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; @@ -47,9 +48,15 @@ public final class LoanApplicationCommandFromApiJsonHelper { */ final Set supportedParameters = new HashSet<>(Arrays.asList("dateFormat", "locale", "id", "clientId", "groupId", "loanType", "productId", "principal", "loanTermFrequency", "loanTermFrequencyType", "numberOfRepayments", "repaymentEvery", - "repaymentFrequencyType", "repaymentFrequencyNthDayType", "repaymentFrequencyDayOfWeekType", "interestRatePerPeriod", - "amortizationType", "interestType", "interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", - "graceOnPrincipalPayment", "graceOnInterestPayment", + "repaymentFrequencyType", "repaymentFrequencyNthDayType", "repaymentFrequencyDayOfWeekType", + "interestRatePerPeriod", + "amortizationType", + "interestType", + "interestCalculationPeriodType", + "expectedDisbursementDate", + "repaymentsStartingFromDate", + "graceOnPrincipalPayment", + "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", // "submittedOnDate", @@ -67,7 +74,8 @@ public final class LoanApplicationCommandFromApiJsonHelper { "syncDisbursementWithMeeting",// optional "linkAccountId", LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName, - LoanProductConstants.recalculationRestFrequencyDateParamName, "createStandingInstructionAtDisbursement")); + LoanProductConstants.recalculationRestFrequencyDateParamName, + LoanProductConstants.recalculationCompoundingFrequencyDateParamName, "createStandingInstructionAtDisbursement")); private final FromJsonHelper fromApiJsonHelper; private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper; @@ -933,11 +941,11 @@ public final class LoanApplicationCommandFromApiJsonHelper { } public void validateRecalcuationFrequency(final LocalDate recalculationFrequencyDate, final LocalDate expectedDisbursementDate, - final List dataValidationErrors) { + final List dataValidationErrors, final String paramName) { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); - baseDataValidator.reset().parameter(LoanProductConstants.recalculationRestFrequencyDateParamName).value(recalculationFrequencyDate) - .notNull().validateDateBeforeOrEqual(expectedDisbursementDate); + baseDataValidator.reset().parameter(paramName).value(recalculationFrequencyDate).notNull() + .validateDateBeforeOrEqual(expectedDisbursementDate); } public void validateLoanCharges(final Set charges, final List dataValidationErrors) { @@ -974,12 +982,32 @@ public final class LoanApplicationCommandFromApiJsonHelper { } } - public void validateLoanForInterestRecalculation(final LocalDate recalculationFrequencyDate, final LocalDate expectedDisbursementDate, - final Set charges) { + public void validateLoanForInterestRecalculation(final Loan loan) { final List dataValidationErrors = new ArrayList<>(); - validateRecalcuationFrequency(recalculationFrequencyDate, expectedDisbursementDate, dataValidationErrors); - validateLoanCharges(charges, dataValidationErrors); + LoanInterestRecalculationDetails interestRecalculationDetails = loan.loanInterestRecalculationDetails(); + if (!interestRecalculationDetails.getRestFrequencyType().isSameAsRepayment()) { + String paramName = LoanProductConstants.recalculationRestFrequencyDateParamName; + validateRecalcuationFrequency(interestRecalculationDetails.getRestFrequencyLocalDate(), loan.getExpectedDisbursedOnLocalDate(), + dataValidationErrors, paramName); + } + + if (interestRecalculationDetails.getInterestRecalculationCompoundingMethod().isCompoundingEnabled() + && !interestRecalculationDetails.getCompoundingFrequencyType().isSameAsRepayment()) { + String paramName = LoanProductConstants.recalculationCompoundingFrequencyDateParamName; + validateCompoundingFrequency(interestRecalculationDetails.getCompoundingFrequencyLocalDate(), + loan.getExpectedDisbursedOnLocalDate(), dataValidationErrors, paramName); + } + + validateLoanCharges(loan.charges(), dataValidationErrors); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + public void validateCompoundingFrequency(final LocalDate recalculationFrequencyDate, final LocalDate expectedDisbursementDate, + final List dataValidationErrors, final String paramName) { + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + baseDataValidator.reset().parameter(paramName).value(recalculationFrequencyDate).notNull() + .validateDateForEqual(expectedDisbursementDate); + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 12991be43..ed21be812 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -232,11 +232,11 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa validateSubmittedOnDate(newLoanApplication); final LoanProductRelatedDetail productRelatedDetail = newLoanApplication.repaymentScheduleDetail(); - - if(loanProduct.getLoanProductConfigurableAttributes() != null){ - updateProductRelatedDetails(productRelatedDetail,newLoanApplication); - } - + + if (loanProduct.getLoanProductConfigurableAttributes() != null) { + updateProductRelatedDetails(productRelatedDetail, newLoanApplication); + } + this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(newLoanApplication.getTermFrequency(), newLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(), productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(), @@ -245,13 +245,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.loanRepository.save(newLoanApplication); if (loanProduct.isInterestRecalculationEnabled()) { - - final LocalDate recalculationFrequencyDate = this.fromJsonHelper.extractLocalDateNamed( - LoanProductConstants.recalculationRestFrequencyDateParamName, command.parsedJson()); - if (!newLoanApplication.loanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment()) { - this.fromApiJsonDeserializer.validateLoanForInterestRecalculation(recalculationFrequencyDate, - newLoanApplication.getExpectedDisbursedOnLocalDate(), newLoanApplication.charges()); - } + this.fromApiJsonDeserializer.validateLoanForInterestRecalculation(newLoanApplication); createAndPersistCalendarInstanceForInterestRecalculation(newLoanApplication); } @@ -306,43 +300,46 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } } - private void updateProductRelatedDetails(LoanProductRelatedDetail productRelatedDetail, - Loan loan){ - final Boolean amortization = loan.loanProduct().getLoanProductConfigurableAttributes().getAmortizationBoolean(); + private void updateProductRelatedDetails(LoanProductRelatedDetail productRelatedDetail, Loan loan) { + final Boolean amortization = loan.loanProduct().getLoanProductConfigurableAttributes().getAmortizationBoolean(); final Boolean arrearsTolerance = loan.loanProduct().getLoanProductConfigurableAttributes().getArrearsToleranceBoolean(); final Boolean graceOnArrearsAging = loan.loanProduct().getLoanProductConfigurableAttributes().getGraceOnArrearsAgingBoolean(); - final Boolean interestCalcPeriod = loan.loanProduct().getLoanProductConfigurableAttributes().getInterestCalcPeriodBoolean(); - final Boolean interestMethod = loan.loanProduct().getLoanProductConfigurableAttributes().getInterestMethodBoolean(); - final Boolean graceOnPrincipalAndInterestPayment = loan.loanProduct().getLoanProductConfigurableAttributes().getGraceOnPrincipalAndInterestPaymentBoolean(); - final Boolean repaymentEvery = loan.loanProduct().getLoanProductConfigurableAttributes().getRepaymentEveryBoolean(); - final Boolean transactionProcessingStrategy = loan.loanProduct().getLoanProductConfigurableAttributes().getTransactionProcessingStrategyBoolean(); - - if(!amortization){ - productRelatedDetail.setAmortizationMethod(loan.loanProduct().getLoanProductRelatedDetail().getAmortizationMethod()); - } - if(!arrearsTolerance){ - productRelatedDetail.setInArrearsTolerance(loan.loanProduct().getLoanProductRelatedDetail().getArrearsTolerance()); - } - if(!graceOnArrearsAging){ - productRelatedDetail.setGraceOnArrearsAgeing(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing()); - } - if(!interestCalcPeriod){ - productRelatedDetail.setInterestCalculationPeriodMethod(loan.loanProduct().getLoanProductRelatedDetail().getInterestCalculationPeriodMethod()); - } - if(!interestMethod){ - productRelatedDetail.setInterestMethod(loan.loanProduct().getLoanProductRelatedDetail().getInterestMethod()); - } - if(!graceOnPrincipalAndInterestPayment){ - productRelatedDetail.setGraceOnInterestPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnInterestPayment()); - productRelatedDetail.setGraceOnPrincipalPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnPrincipalPayment()); - } - if(!repaymentEvery){ - productRelatedDetail.setRepayEvery(loan.loanProduct().getLoanProductRelatedDetail().getRepayEvery()); - } - if(!transactionProcessingStrategy){ - loan.updateTransactionProcessingStrategy(loan.loanProduct().getRepaymentStrategy()); - } + final Boolean interestCalcPeriod = loan.loanProduct().getLoanProductConfigurableAttributes().getInterestCalcPeriodBoolean(); + final Boolean interestMethod = loan.loanProduct().getLoanProductConfigurableAttributes().getInterestMethodBoolean(); + final Boolean graceOnPrincipalAndInterestPayment = loan.loanProduct().getLoanProductConfigurableAttributes() + .getGraceOnPrincipalAndInterestPaymentBoolean(); + final Boolean repaymentEvery = loan.loanProduct().getLoanProductConfigurableAttributes().getRepaymentEveryBoolean(); + final Boolean transactionProcessingStrategy = loan.loanProduct().getLoanProductConfigurableAttributes() + .getTransactionProcessingStrategyBoolean(); + + if (!amortization) { + productRelatedDetail.setAmortizationMethod(loan.loanProduct().getLoanProductRelatedDetail().getAmortizationMethod()); + } + if (!arrearsTolerance) { + productRelatedDetail.setInArrearsTolerance(loan.loanProduct().getLoanProductRelatedDetail().getArrearsTolerance()); + } + if (!graceOnArrearsAging) { + productRelatedDetail.setGraceOnArrearsAgeing(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing()); + } + if (!interestCalcPeriod) { + productRelatedDetail.setInterestCalculationPeriodMethod(loan.loanProduct().getLoanProductRelatedDetail() + .getInterestCalculationPeriodMethod()); + } + if (!interestMethod) { + productRelatedDetail.setInterestMethod(loan.loanProduct().getLoanProductRelatedDetail().getInterestMethod()); + } + if (!graceOnPrincipalAndInterestPayment) { + productRelatedDetail.setGraceOnInterestPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnInterestPayment()); + productRelatedDetail.setGraceOnPrincipalPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnPrincipalPayment()); + } + if (!repaymentEvery) { + productRelatedDetail.setRepayEvery(loan.loanProduct().getLoanProductRelatedDetail().getRepayEvery()); + } + if (!transactionProcessingStrategy) { + loan.updateTransactionProcessingStrategy(loan.loanProduct().getRepaymentStrategy()); + } } + private void createAndPersistCalendarInstanceForInterestRecalculation(final Loan loan) { LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getRestFrequencyLocalDate(); @@ -353,6 +350,34 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final RecalculationFrequencyType recalculationFrequencyType = loan.loanInterestRecalculationDetails().getRestFrequencyType(); Integer frequency = loan.loanInterestRecalculationDetails().getRestInterval(); + CalendarEntityType calendarEntityType = CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL; + final String title = "loan_recalculation_detail_" + loan.loanInterestRecalculationDetails().getId(); + + createCalendar(loan, calendarStartDate, repeatsOnDay, recalculationFrequencyType, frequency, calendarEntityType, title); + + if (loan.loanInterestRecalculationDetails().getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { + LocalDate compoundingStartDate = loan.loanInterestRecalculationDetails().getCompoundingFrequencyLocalDate(); + if (compoundingStartDate == null) { + compoundingStartDate = loan.getExpectedDisbursedOnLocalDate(); + } + final Integer compoundingRepeatsOnDay = compoundingStartDate.getDayOfWeek(); + final RecalculationFrequencyType recalculationCompoundingFrequencyType = loan.loanInterestRecalculationDetails() + .getCompoundingFrequencyType(); + + Integer compoundingFrequency = loan.loanInterestRecalculationDetails().getCompoundingInterval(); + CalendarEntityType compoundingCalendarEntityType = CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL; + final String compoundingCalendarTitle = "loan_recalculation_detail_compounding_frequency" + + loan.loanInterestRecalculationDetails().getId(); + + createCalendar(loan, compoundingStartDate, compoundingRepeatsOnDay, recalculationCompoundingFrequencyType, + compoundingFrequency, compoundingCalendarEntityType, compoundingCalendarTitle); + } + + } + + private void createCalendar(final Loan loan, LocalDate calendarStartDate, final Integer repeatsOnDay, + final RecalculationFrequencyType recalculationFrequencyType, Integer frequency, CalendarEntityType calendarEntityType, + final String title) { CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID; switch (recalculationFrequencyType) { case DAILY: @@ -373,16 +398,14 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa break; } - final String title = "loan_recalculation_detail_" + loan.loanInterestRecalculationDetails().getId(); final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), calendarFrequencyType, frequency, repeatsOnDay); final CalendarInstance calendarInstance = CalendarInstance.from(calendar, loan.loanInterestRecalculationDetails().getId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + calendarEntityType.getValue()); this.calendarInstanceRepository.save(calendarInstance); - } - private void updateCalendarDetailsForInterestRecalculation(final CalendarInstance calendarInstance, final Loan loan) { + private void updateRestCalendarDetailsForInterestRecalculation(final CalendarInstance calendarInstance, final Loan loan) { Calendar interestRecalculationRecurrings = calendarInstance.getCalendar(); LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getRestFrequencyLocalDate(); @@ -393,6 +416,29 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final RecalculationFrequencyType recalculationFrequencyType = loan.loanInterestRecalculationDetails().getRestFrequencyType(); Integer frequency = loan.loanInterestRecalculationDetails().getRestInterval(); + + updateCalendar(loan, interestRecalculationRecurrings, calendarStartDate, repeatsOnDay, recalculationFrequencyType, frequency); + + } + + private void updateCompoundingCalendarDetailsForInterestRecalculation(final CalendarInstance calendarInstance, final Loan loan) { + + Calendar interestRecalculationRecurrings = calendarInstance.getCalendar(); + LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getCompoundingFrequencyLocalDate(); + if (calendarStartDate == null) { + calendarStartDate = loan.getExpectedDisbursedOnLocalDate(); + } + final Integer repeatsOnDay = calendarStartDate.getDayOfWeek(); + final RecalculationFrequencyType recalculationFrequencyType = loan.loanInterestRecalculationDetails().getCompoundingFrequencyType(); + + Integer frequency = loan.loanInterestRecalculationDetails().getCompoundingInterval(); + + updateCalendar(loan, interestRecalculationRecurrings, calendarStartDate, repeatsOnDay, recalculationFrequencyType, frequency); + + } + + private void updateCalendar(final Loan loan, Calendar interestRecalculationRecurrings, LocalDate calendarStartDate, + final Integer repeatsOnDay, final RecalculationFrequencyType recalculationFrequencyType, Integer frequency) { CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID; switch (recalculationFrequencyType) { case DAILY: @@ -416,7 +462,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa interestRecalculationRecurrings.updateRepeatingCalendar(calendarStartDate, calendarFrequencyType, frequency, repeatsOnDay); this.calendarRepository.save(interestRecalculationRecurrings); - } @Transactional @@ -503,10 +548,10 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } final String productIdParamName = "productId"; - if (changes.containsKey(productIdParamName)) { - final Long productId = command.longValueOfParameterNamed(productIdParamName); - final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); - if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } + if (changes.containsKey(productIdParamName)) { + final Long productId = command.longValueOfParameterNamed(productIdParamName); + final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); + if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } existingLoanApplication.updateLoanProduct(loanProduct); if (!changes.containsKey("interestRateFrequencyType")) { @@ -537,10 +582,10 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa validateSubmittedOnDate(existingLoanApplication); final LoanProductRelatedDetail productRelatedDetail = existingLoanApplication.repaymentScheduleDetail(); - if(existingLoanApplication.loanProduct().getLoanProductConfigurableAttributes() != null){ - updateProductRelatedDetails(productRelatedDetail,existingLoanApplication); - } - + if (existingLoanApplication.loanProduct().getLoanProductConfigurableAttributes() != null) { + updateProductRelatedDetails(productRelatedDetail, existingLoanApplication); + } + final String fundIdParamName = "fundId"; if (changes.containsKey(fundIdParamName)) { final Long fundId = command.longValueOfParameterNamed(fundIdParamName); @@ -693,30 +738,23 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.loanRepository.save(existingLoanApplication); if (productRelatedDetail.isInterestRecalculationEnabled()) { - LocalDate recalculationFrequencyDate = existingLoanApplication.loanInterestRecalculationDetails() - .getRestFrequencyLocalDate(); - if (recalculationFrequencyDate == null) { - recalculationFrequencyDate = existingLoanApplication.loanProduct().getProductInterestRecalculationDetails() - .getRestFrequencyLocalDate(); - } - if (this.fromJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyDateParamName, command.parsedJson())) { - recalculationFrequencyDate = this.fromJsonHelper.extractLocalDateNamed( - LoanProductConstants.recalculationRestFrequencyDateParamName, command.parsedJson()); - if (!existingLoanApplication.loanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment()) { - this.fromApiJsonDeserializer.validateLoanForInterestRecalculation(recalculationFrequencyDate, - existingLoanApplication.getExpectedDisbursedOnLocalDate(), existingLoanApplication.charges()); - } - } - - CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId( - existingLoanApplication.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), CalendarType.COLLECTION.getValue()); - - if (calendarInstance == null) { + this.fromApiJsonDeserializer.validateLoanForInterestRecalculation(existingLoanApplication); + if (changes.containsKey(LoanProductConstants.isInterestRecalculationEnabledParameterName)) { createAndPersistCalendarInstanceForInterestRecalculation(existingLoanApplication); - } else { - updateCalendarDetailsForInterestRecalculation(calendarInstance, existingLoanApplication); + if (changes.containsKey(LoanProductConstants.recalculationRestFrequencyDateParamName)) { + + CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId( + existingLoanApplication.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue(), CalendarType.COLLECTION.getValue()); + updateRestCalendarDetailsForInterestRecalculation(calendarInstance, existingLoanApplication); + } + if (changes.containsKey(LoanProductConstants.recalculationCompoundingFrequencyDateParamName)) { + CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId( + existingLoanApplication.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue(), CalendarType.COLLECTION.getValue()); + updateCompoundingCalendarDetailsForInterestRecalculation(calendarInstance, existingLoanApplication); + } } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index 72e7cc966..19bb2a024 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -270,6 +270,8 @@ public class LoanAssembler { final LocalDate recalculationRestFrequencyDate = this.fromApiJsonHelper.extractLocalDateNamed( LoanProductConstants.recalculationRestFrequencyDateParamName, element); + final LocalDate recalculationCompoundingFrequencyDate = this.fromApiJsonHelper.extractLocalDateNamed( + LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element); final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); @@ -282,7 +284,7 @@ public class LoanAssembler { isHolidayEnabled, holidays, workingDays, element); loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(), submittedOnDate, externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, - recalculationRestFrequencyDate); + recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate); return loanApplication; } @@ -313,7 +315,7 @@ public class LoanAssembler { && StringUtils.isNotBlank((jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString()))) { principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal(); } - + disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal)); i++; } while (i < disbursementDataArray.size()); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 0a2e9b0b5..a65c4a34a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -82,6 +82,7 @@ import org.mifosplatform.portfolio.loanaccount.data.PaidInAdvanceData; import org.mifosplatform.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType; @@ -140,6 +141,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private final ConfigurationDomainService configurationDomainService; private final WorkingDaysRepositoryWrapper workingDaysRepository; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; + private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; @Autowired public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository, @@ -152,7 +154,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService, final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository, final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, - final WorkingDaysRepositoryWrapper workingDaysRepository, PaymentTypeReadPlatformService paymentTypeReadPlatformService) { + final WorkingDaysRepositoryWrapper workingDaysRepository, PaymentTypeReadPlatformService paymentTypeReadPlatformService, + final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) { this.context = context; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -174,6 +177,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.configurationDomainService = configurationDomainService; this.workingDaysRepository = workingDaysRepository; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; + this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; } @Override @@ -404,6 +408,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Loan loan = this.loanRepository.findOne(loanId); if (loan == null) { throw new LoanNotFoundException(loanId); } + loan.setHelpers(null, null, loanRepaymentScheduleTransactionProcessorFactory); final MonetaryCurrency currency = loan.getCurrency(); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -413,10 +418,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate earliestUnpaidInstallmentDate = LocalDate.now(); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; HolidayDetailDTO holidayDetailDTO = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); @@ -425,7 +433,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(this.loanScheduleFactory, - restCalendarInstance, holidayDetailDTO, onDate); + restCalendarInstance, compoundingCalendarInstance, holidayDetailDTO, onDate); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT); final Collection paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(); @@ -600,6 +608,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " lir.id as lirId, lir.loan_id as loanId, lir.compound_type_enum as compoundType, lir.reschedule_strategy_enum as rescheduleStrategy, " + " lir.rest_frequency_type_enum as restFrequencyEnum, lir.rest_frequency_interval as restFrequencyInterval, " + " lir.rest_freqency_date as restFrequencyDate, " + + " lir.compounding_frequency_type_enum as compoundingFrequencyEnum, lir.compounding_frequency_interval as compoundingInterval, " + + " lir.compounding_freqency_date as compoundingFrequencyDate, " + " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement " + " from m_loan l" // + " join m_product_loan lp on lp.id = l.product_id" // @@ -857,9 +867,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final EnumOptionData restFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(restFrequencyEnumValue); final int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval"); final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate"); + final CalendarData compoundingCalendarData = null; + final Integer compoundingFrequencyEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyEnum"); + EnumOptionData compoundingFrequencyType = null; + if (compoundingFrequencyEnumValue != null) { + compoundingFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(compoundingFrequencyEnumValue); + } + final Integer compoundingInterval = JdbcSupport.getInteger(rs, "compoundingInterval"); + final LocalDate compoundingFrequencyDate = JdbcSupport.getLocalDate(rs, "compoundingFrequencyDate"); interestRecalculationData = new LoanInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType, - rescheduleStrategyType, calendarData, restFrequencyType, restFrequencyInterval, restFrequencyDate); + rescheduleStrategyType, calendarData, restFrequencyType, restFrequencyInterval, restFrequencyDate, + compoundingCalendarData, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate); } return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData, 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 90841c6da..34ab7e2f7 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 @@ -340,19 +340,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; LocalDate recalculateFrom = null; Long overdurPenaltyWaitPeriod = null; - LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_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); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, + recalculateFrom, overdurPenaltyWaitPeriod); regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { @@ -472,7 +474,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf && loan.loanInterestRecalculationDetails().getRestFrequencyType().isSameAsRepayment() && !loan.isDisbursed()) { final CalendarInstance calendarInstanceForInterestRecalculation = this.calendarInstanceRepository .findByEntityIdAndEntityTypeIdAndCalendarTypeId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), CalendarType.COLLECTION.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue(), CalendarType.COLLECTION.getValue()); Calendar calendarForInterestRecalculation = calendarInstanceForInterestRecalculation.getCalendar(); calendarForInterestRecalculation.updateStartAndEndDate(loan.getDisbursementDate(), loan.getMaturityDate()); @@ -595,19 +597,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf loan.getLoanTransactions().add(disbursementTransaction); } CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; LocalDate recalculateFrom = null; Long overdurPenaltyWaitPeriod = null; - LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( - loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - firstRepaymentOnDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, - lastTransactionDate); + firstRepaymentOnDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive( @@ -697,13 +701,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); final Map changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, currentUser); @@ -860,16 +867,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; 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()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final MonetaryCurrency currency = loan.getCurrency(); applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -885,8 +894,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, - lastTransactionDate); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, @@ -990,6 +999,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_WAIVE_INTEREST, constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, waiveInterestTransaction)); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; ApplicationCurrency applicationCurrency = null; LocalDate calculatedRepaymentsStartingFromDate = null; List holidays = null; @@ -997,10 +1007,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf 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()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final MonetaryCurrency currency = loan.getCurrency(); applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -1017,8 +1028,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, - lastTransactionDate); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); @@ -1099,18 +1110,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; LocalDate recalculateFrom = null; Long overdurPenaltyWaitPeriod = null; - LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_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); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); @@ -1178,20 +1191,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; LocalDate recalculateFrom = null; Long overdurPenaltyWaitPeriod = null; - LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_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); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); @@ -1376,7 +1391,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf AppUser currentUser = getAppUserIfPresent(); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( - loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + CalendarInstance compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final MonetaryCurrency currency = loan.getCurrency(); ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -1392,12 +1409,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf 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); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, + recalculateFrom, overdurPenaltyWaitPeriod); ChangedTransactionDetail changedTransactionDetail = loan.handleRegenerateRepaymentScheduleWithInterestRecalculation( generatorDTO, currentUser); saveLoanWithDataIntegrityViolationChecks(loan); @@ -1431,13 +1446,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); createLoanScheduleArchive(loan, scheduleGeneratorDTO); } @@ -1564,17 +1582,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; ApplicationCurrency applicationCurrency = null; LocalDate calculatedRepaymentsStartingFromDate = null; List holidays = null; boolean isHolidayEnabled = false; WorkingDays workingDays = null; LocalDate recalculateFrom = null; - LocalDate lastTransactionDate = null; Long overdurPenaltyWaitPeriod = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final MonetaryCurrency currency = loan.getCurrency(); applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -1591,8 +1611,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, - lastTransactionDate); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); Money accruedCharge = Money.zero(loan.getCurrency()); if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { @@ -2510,6 +2530,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf "error.msg.loan.status.not.written.off.update.not.allowed", "Loan :" + loanId + " update not allowed as loan status is not written off", loanId); } CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; ApplicationCurrency applicationCurrency = null; LocalDate calculatedRepaymentsStartingFromDate = null; List holidays = null; @@ -2517,13 +2538,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf WorkingDays workingDays = null; LocalDate recalculateFrom = null; Long overdurPenaltyWaitPeriod = null; - LocalDate lastTransactionDate = null; LoanTransaction writeOffTransaction = loan.findWriteOffTransaction(); this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_UNDO_WRITTEN_OFF, constructEntityMap(BUSINESS_ENTITY.LOAN_TRANSACTION, writeOffTransaction)); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final MonetaryCurrency currency = loan.getCurrency(); applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -2540,8 +2562,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, - lastTransactionDate); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); @@ -2655,19 +2677,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List existingReversedTransactionIds = new ArrayList<>(); final Map changes = new LinkedHashMap<>(); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; LocalDate recalculateFrom = null; Long overdurPenaltyWaitPeriod = null; - LocalDate lastTransactionDate = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_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); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); ChangedTransactionDetail changedTransactionDetail = null; AppUser currentUser = getAppUserIfPresent(); @@ -2815,6 +2839,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION, constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); CalendarInstance restCalendarInstance = null; + CalendarInstance compoundingCalendarInstance = null; ApplicationCurrency applicationCurrency = null; LocalDate calculatedRepaymentsStartingFromDate = null; List holidays = null; @@ -2822,12 +2847,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf 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()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final MonetaryCurrency currency = loan.getCurrency(); applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); @@ -2843,8 +2869,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod, - lastTransactionDate); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, + overdurPenaltyWaitPeriod); ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO, existingTransactionIds, existingReversedTransactionIds, currentUser); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java index 8e613f28d..9cc4c9edf 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java @@ -76,6 +76,9 @@ public interface LoanProductConstants { public static final String recalculationRestFrequencyDateParamName = "recalculationRestFrequencyDate"; public static final String isArrearsBasedOnOriginalScheduleParamName = "isArrearsBasedOnOriginalSchedule"; public static final String preClosureInterestCalculationStrategyParamName = "preClosureInterestCalculationStrategy"; + public static final String recalculationCompoundingFrequencyTypeParameterName = "recalculationCompoundingFrequencyType"; + public static final String recalculationCompoundingFrequencyIntervalParameterName = "recalculationCompoundingFrequencyInterval"; + public static final String recalculationCompoundingFrequencyDateParamName = "recalculationCompoundingFrequencyDate"; // Guarantee related public static final String holdGuaranteeFundsParamName = "holdGuaranteeFunds"; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index 7c4cc0c3b..dfc44096c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -370,7 +370,8 @@ public class LoanProductData { multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment, - accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, loanProductConfigurableAttributes); + accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, + loanProductConfigurableAttributes); } @@ -827,9 +828,11 @@ public class LoanProductData { final Long id = null; final Long loanId = null; final CalendarData calendarData = null; + final CalendarData compoundingCalendarData = null; return new LoanInterestRecalculationData(id, loanId, getInterestRecalculationCompoundingType(), getRescheduleStrategyType(), calendarData, getRecalculationRestFrequencyType(), getRecalculationRestFrequencyInterval(), - getRecalculationRestFrequencyDate()); + getRecalculationRestFrequencyDate(), compoundingCalendarData, getRecalculationCompoundingFrequencyType(), + getRecalculationCompoundingFrequencyInterval(), getRecalculationCompoundingFrequencyDate()); } private EnumOptionData getRescheduleStrategyType() { @@ -847,20 +850,35 @@ public class LoanProductData { return null; } - public EnumOptionData getRecalculationRestFrequencyType() { + private EnumOptionData getRecalculationRestFrequencyType() { if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyType(); } return null; } - public Integer getRecalculationRestFrequencyInterval() { + private Integer getRecalculationRestFrequencyInterval() { if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyInterval(); } return null; } + private LocalDate getRecalculationCompoundingFrequencyDate() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyDate(); } + return null; + } + + private EnumOptionData getRecalculationCompoundingFrequencyType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyType(); } + return null; + } + + private Integer getRecalculationCompoundingFrequencyInterval() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyInterval(); } + return null; + } + public boolean canDefineInstallmentAmount() { return this.canDefineInstallmentAmount; } - + public LoanProductConfigurableAttributes getloanProductConfigurableAttributes() { return this.allowAttributeOverrides; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java index cdba0e48a..11f4293ec 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java @@ -26,6 +26,9 @@ public class LoanProductInterestRecalculationData { private final EnumOptionData recalculationRestFrequencyType; private final Integer recalculationRestFrequencyInterval; private final LocalDate recalculationRestFrequencyDate; + private final EnumOptionData recalculationCompoundingFrequencyType; + private final Integer recalculationCompoundingFrequencyInterval; + private final LocalDate recalculationCompoundingFrequencyDate; @SuppressWarnings("unused") private final boolean isArrearsBasedOnOriginalSchedule; @SuppressWarnings("unused") @@ -34,8 +37,9 @@ public class LoanProductInterestRecalculationData { public LoanProductInterestRecalculationData(final Long id, final Long productId, final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType, final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval, - final LocalDate recalculationRestFrequencyDate, boolean isArrearsBasedOnOriginalSchedule, - final EnumOptionData preCloseInterestCalculationStrategy) { + final LocalDate recalculationRestFrequencyDate, final EnumOptionData recalculationCompoundingFrequencyType, + final Integer recalculationCompoundingFrequencyInterval, final LocalDate recalculationCompoundingFrequencyDate, + final boolean isArrearsBasedOnOriginalSchedule, final EnumOptionData preCloseInterestCalculationStrategy) { this.id = id; this.productId = productId; this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; @@ -43,6 +47,9 @@ public class LoanProductInterestRecalculationData { this.recalculationRestFrequencyType = recalculationRestFrequencyType; this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval; this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; + this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType; + this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval; + this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate; this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule; this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy; } @@ -55,10 +62,14 @@ public class LoanProductInterestRecalculationData { final EnumOptionData recalculationRestFrequencyType = null; final Integer recalculationRestFrequencyInterval = null; final LocalDate recalculationRestFrequencyDate = null; + final EnumOptionData recalculationCompoundingFrequencyType = null; + final Integer recalculationCompoundingFrequencyInterval = null; + final LocalDate recalculationCompoundingFrequencyDate = null; final boolean isArrearsBasedOnOriginalSchedule = false; final EnumOptionData preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE); return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType, recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate, + recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy); } @@ -82,4 +93,16 @@ public class LoanProductInterestRecalculationData { return this.recalculationRestFrequencyInterval; } + public EnumOptionData getRecalculationCompoundingFrequencyType() { + return this.recalculationCompoundingFrequencyType; + } + + public Integer getRecalculationCompoundingFrequencyInterval() { + return this.recalculationCompoundingFrequencyInterval; + } + + public LocalDate getRecalculationCompoundingFrequencyDate() { + return this.recalculationCompoundingFrequencyDate; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java index 06d81c0b7..17c95cc2a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java @@ -58,6 +58,16 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable @Column(name = "rest_freqency_date") private Date restFrequencyDate; + @Column(name = "compounding_frequency_type_enum", nullable = true) + private Integer compoundingFrequencyType; + + @Column(name = "compounding_frequency_interval", nullable = true) + private Integer compoundingInterval; + + @Temporal(TemporalType.DATE) + @Column(name = "compounding_freqency_date") + private Date compoundingFrequencyDate; + @Column(name = "arrears_based_on_original_schedule") private boolean isArrearsBasedOnOriginalSchedule; @@ -95,6 +105,30 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable recurrenceInterval = 0; } + InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod + .fromInt(interestRecalculationCompoundingMethod); + Integer compoundingRecurrenceFrequency = null; + Integer compoundingInterval = null; + Date recurrenceOnCompoundingDate = null; + if (compoundingMethod.isCompoundingEnabled()) { + compoundingRecurrenceFrequency = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName); + compoundingInterval = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName); + RecalculationFrequencyType compoundingFrequencyType = RecalculationFrequencyType.fromInt(compoundingRecurrenceFrequency); + if (compoundingFrequencyType.isSameAsRepayment()) { + recurrenceInterval = 0; + } + final LocalDate compoundingRecurrenceOnLocalDate = command + .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName); + + if (compoundingRecurrenceOnLocalDate != null) { + if (!compoundingFrequencyType.isSameAsRepayment()) { + recurrenceOnCompoundingDate = compoundingRecurrenceOnLocalDate.toDate(); + } + } + } + Integer preCloseInterestCalculationStrategy = command .integerValueOfParameterNamed(LoanProductConstants.preClosureInterestCalculationStrategyParamName); if (preCloseInterestCalculationStrategy == null) { @@ -102,18 +136,22 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable } return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod, - recurrenceFrequency, recurrenceInterval, recurrenceOnDate, isArrearsBasedOnOriginalSchedule, - preCloseInterestCalculationStrategy); + recurrenceFrequency, recurrenceInterval, recurrenceOnDate, compoundingRecurrenceFrequency, compoundingInterval, + recurrenceOnCompoundingDate, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy); } private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval, - final Date restFrequencyDate, final boolean isArrearsBasedOnOriginalSchedule, final Integer preCloseInterestCalculationStrategy) { + final Date restFrequencyDate, Integer compoundingFrequencyType, Integer compoundingInterval, Date compoundingFrequencyDate, + final boolean isArrearsBasedOnOriginalSchedule, final Integer preCloseInterestCalculationStrategy) { this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; this.rescheduleStrategyMethod = rescheduleStrategyMethod; this.restFrequencyType = restFrequencyType; this.restInterval = restInterval; this.restFrequencyDate = restFrequencyDate; + this.compoundingFrequencyDate = compoundingFrequencyDate; + this.compoundingFrequencyType = compoundingFrequencyType; + this.compoundingInterval = compoundingInterval; this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule; this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy; } @@ -165,9 +203,6 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable this.restInterval)) { Integer newValue = command .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName); - if (frequencyType.isSameAsRepayment()) { - newValue = 0; - } actualChanges.put(LoanProductConstants.recalculationRestFrequencyIntervalParameterName, newValue); actualChanges.put("locale", localeAsInput); this.restInterval = newValue; @@ -179,14 +214,55 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); Date recurrenceOnDate = null; if (newValue != null) { - if (!frequencyType.isSameAsRepayment()) { - recurrenceOnDate = newValue.toDate(); - } + recurrenceOnDate = newValue.toDate(); } actualChanges.put(LoanProductConstants.recalculationRestFrequencyDateParamName, newValue); this.restFrequencyDate = recurrenceOnDate; } } + + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, + this.compoundingFrequencyType)) { + final Integer newValue = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName); + actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, newValue); + this.compoundingFrequencyType = RecalculationFrequencyType.fromInt(newValue).getValue(); + } + + InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod + .fromInt(this.interestRecalculationCompoundingMethod); + if (compoundingMethod.isCompoundingEnabled()) { + RecalculationFrequencyType compoundingfrequencyType = RecalculationFrequencyType.fromInt(this.compoundingFrequencyType); + if (compoundingfrequencyType.isSameAsRepayment()) { + this.compoundingInterval = null; + this.compoundingFrequencyDate = null; + } else { + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, + this.compoundingInterval)) { + Integer newValue = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName); + actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, newValue); + this.compoundingInterval = newValue; + } + + if (command.isChangeInLocalDateParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, + getCompoundingFrequencyLocalDate())) { + final LocalDate newValue = command + .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName); + Date recurrenceOnDate = null; + if (newValue != null) { + recurrenceOnDate = newValue.toDate(); + } + actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, newValue); + this.compoundingFrequencyDate = recurrenceOnDate; + } + } + } else { + this.compoundingFrequencyType = null; + this.compoundingInterval = null; + this.compoundingFrequencyDate = null; + } + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, this.isArrearsBasedOnOriginalSchedule)) { final boolean newValue = command @@ -223,6 +299,22 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable return this.restInterval; } + public LocalDate getCompoundingFrequencyLocalDate() { + LocalDate recurrenceOnLocalDate = null; + if (this.compoundingFrequencyDate != null) { + recurrenceOnLocalDate = new LocalDate(this.compoundingFrequencyDate); + } + return recurrenceOnLocalDate; + } + + public RecalculationFrequencyType getCompoundingFrequencyType() { + return RecalculationFrequencyType.fromInt(this.compoundingFrequencyType); + } + + public Integer getCompoundingInterval() { + return this.compoundingInterval; + } + public boolean isArrearsBasedOnOriginalSchedule() { return this.isArrearsBasedOnOriginalSchedule; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/RecalculationFrequencyType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/RecalculationFrequencyType.java index bcf3375c9..c21e1b5f5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/RecalculationFrequencyType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/RecalculationFrequencyType.java @@ -8,6 +8,8 @@ package org.mifosplatform.portfolio.loanproduct.domain; import java.util.HashMap; import java.util.Map; +import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; + public enum RecalculationFrequencyType { INVALID(0, "interestRecalculationFrequencyType.invalid"), // SAME_AS_REPAYMENT_PERIOD(1, "interestRecalculationFrequencyType.same.as.repayment.period"), // @@ -59,4 +61,23 @@ public enum RecalculationFrequencyType { public boolean isMonthly() { return this.value.equals(RecalculationFrequencyType.MONTHLY.getValue()); } + + public boolean isSameFrequency(final PeriodFrequencyType frequencyType) { + boolean isSameFre = false; + switch (this) { + case DAILY: + isSameFre = frequencyType.isDaily(); + break; + case MONTHLY: + isSameFre = frequencyType.isMonthly(); + break; + case WEEKLY: + isSameFre = frequencyType.isWeekly(); + break; + default: + break; + } + + return isSameFre; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 36e97eff5..2f9fb449c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -25,8 +25,10 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.exception.InvalidJsonException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy; import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; @@ -74,6 +76,9 @@ public final class LoanProductDataValidator { LoanProductConstants.recalculationRestFrequencyDateParamName, LoanProductConstants.recalculationRestFrequencyIntervalParameterName, LoanProductConstants.recalculationRestFrequencyTypeParameterName, + LoanProductConstants.recalculationCompoundingFrequencyDateParamName, + LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, + LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, LoanProductConstants.minimumDaysBetweenDisbursalAndFirstRepayment, LoanProductConstants.mandatoryGuaranteeParamName, LoanProductConstants.holdGuaranteeFundsParamName, LoanProductConstants.minimumGuaranteeFromGuarantorParamName, @@ -510,6 +515,8 @@ public final class LoanProductDataValidator { /** * { @link InterestRecalculationCompoundingMethod } */ + InterestRecalculationCompoundingMethod compoundingMethod = null; + if (loanProduct == null || this.fromApiJsonHelper .parameterExists(LoanProductConstants.interestRecalculationCompoundingMethodParameterName, element)) { @@ -517,7 +524,20 @@ public final class LoanProductDataValidator { LoanProductConstants.interestRecalculationCompoundingMethodParameterName, element, Locale.getDefault()); baseDataValidator.reset().parameter(LoanProductConstants.interestRecalculationCompoundingMethodParameterName) .value(interestRecalculationCompoundingMethod).notNull().inMinMaxRange(0, 3); + if (interestRecalculationCompoundingMethod != null) { + compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(interestRecalculationCompoundingMethod); + } } + + if (compoundingMethod == null) { + if (loanProduct == null) { + compoundingMethod = InterestRecalculationCompoundingMethod.NONE; + } else { + compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(loanProduct.getProductInterestRecalculationDetails() + .getInterestRecalculationCompoundingMethod()); + } + } + /** * { @link LoanRescheduleStrategyMethod } */ @@ -537,7 +557,9 @@ public final class LoanProductDataValidator { LoanProductConstants.recalculationRestFrequencyTypeParameterName, element, Locale.getDefault()); baseDataValidator.reset().parameter(LoanProductConstants.recalculationRestFrequencyTypeParameterName) .value(recalculationRestFrequencyType).notNull().inMinMaxRange(1, 4); - frequencyType = RecalculationFrequencyType.fromInt(recalculationRestFrequencyType); + if (recalculationRestFrequencyType != null) { + frequencyType = RecalculationFrequencyType.fromInt(recalculationRestFrequencyType); + } } if (frequencyType == null) { @@ -566,6 +588,70 @@ public final class LoanProductDataValidator { } } + if (compoundingMethod.isCompoundingEnabled()) { + RecalculationFrequencyType compoundingfrequencyType = null; + + if (loanProduct == null + || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, + element)) { + final Integer recalculationCompoundingFrequencyType = this.fromApiJsonHelper.extractIntegerNamed( + LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName) + .value(recalculationCompoundingFrequencyType).notNull().inMinMaxRange(1, 4); + if (recalculationCompoundingFrequencyType != null) { + compoundingfrequencyType = RecalculationFrequencyType.fromInt(recalculationCompoundingFrequencyType); + if (!compoundingfrequencyType.isSameAsRepayment()) { + PeriodFrequencyType repaymentFrequencyType = null; + if (loanProduct == null) { + Integer repaymentFrequencyTypeVal = this.fromApiJsonHelper.extractIntegerNamed("repaymentFrequencyType", + element, Locale.getDefault()); + repaymentFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyTypeVal); + } else { + repaymentFrequencyType = loanProduct.getLoanProductRelatedDetail().getRepaymentPeriodFrequencyType(); + } + if (!compoundingfrequencyType.isSameFrequency(repaymentFrequencyType)) { + baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName) + .value(recalculationCompoundingFrequencyType).failWithCode("must.be.same.as.repayment.frequency"); + } + } + } + } + + if (compoundingfrequencyType == null) { + if (loanProduct == null) { + compoundingfrequencyType = RecalculationFrequencyType.INVALID; + } else { + compoundingfrequencyType = loanProduct.getProductInterestRecalculationDetails().getCompoundingFrequencyType(); + } + } + + if (!compoundingfrequencyType.isSameAsRepayment()) { + if (loanProduct == null + || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationCompoundingFrequencyDateParamName, + element)) { + final LocalDate recurrenceOnLocalDate = this.fromApiJsonHelper.extractLocalDateNamed( + LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element); + baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyDateParamName) + .value(recurrenceOnLocalDate).notNull(); + } + if (loanProduct == null + || this.fromApiJsonHelper.parameterExists( + LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, element)) { + final Integer recurrenceInterval = this.fromApiJsonHelper.extractIntegerNamed( + LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, element, Locale.getDefault()); + Integer repaymentEvery = null; + if (loanProduct == null) { + repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); + } else { + repaymentEvery = loanProduct.getLoanProductRelatedDetail().getRepayEvery(); + } + + baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName) + .value(recurrenceInterval).notNull().integerInMultiplesOfNumber(repaymentEvery); + } + } + } + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, element)) { final Boolean isArrearsBasedOnOriginalSchedule = this.fromApiJsonHelper.extractBooleanNamed( LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, element); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 61343416e..451f020f9 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -77,7 +77,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final String sql = "select " + rm.schema() + " where bc.loan_product_id=? order by bc.borrower_cycle_number,bc.value_condition"; return this.jdbcTemplate.query(sql, rm, new Object[] { loanProductId }); } - + @Override public Collection retrieveAllLoanProducts() { @@ -166,6 +166,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, " + "lpr.rest_frequency_type_enum as restFrequencyEnum, lpr.rest_frequency_interval as restFrequencyInterval, " + "lpr.rest_freqency_date as restFrequencyDate, lpr.arrears_based_on_original_schedule as isArrearsBasedOnOriginalSchedule, " + + "lpr.compounding_frequency_type_enum as compoundingFrequencyTypeEnum, lpr.compounding_frequency_interval as compoundingInterval, " + + "lpr.compounding_freqency_date as compoundingFrequencyDate, " + "lp.hold_guarantee_funds as holdGuaranteeFunds, " + "lp.principal_threshold_for_last_installment as principalThresholdForLastInstallment, " + "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, " @@ -287,7 +289,6 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final boolean canDefineInstallmentAmount = rs.getBoolean("canDefineInstallmentAmount"); final boolean isInterestRecalculationEnabled = rs.getBoolean("isInterestRecalculationEnabled"); - LoanProductInterestRecalculationData interestRecalculationData = null; if (isInterestRecalculationEnabled) { @@ -302,6 +303,14 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final EnumOptionData restFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(restFrequencyEnumValue); final int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval"); final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate"); + final Integer compoundingFrequencyEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyTypeEnum"); + EnumOptionData compoundingFrequencyType = null; + if (compoundingFrequencyEnumValue != null) { + compoundingFrequencyType = LoanEnumerations + .interestRecalculationFrequencyType(compoundingFrequencyEnumValue); + } + final Integer compoundingInterval = JdbcSupport.getInteger(rs, "compoundingInterval"); + final LocalDate compoundingFrequencyDate = JdbcSupport.getLocalDate(rs, "compoundingFrequencyDate"); final boolean isArrearsBasedOnOriginalSchedule = rs.getBoolean("isArrearsBasedOnOriginalSchedule"); final int preCloseInterestCalculationStrategyEnumValue = JdbcSupport.getInteger(rs, "preCloseInterestCalculationStrategy"); final EnumOptionData preCloseInterestCalculationStrategy = LoanEnumerations @@ -309,7 +318,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType, rescheduleStrategyType, restFrequencyType, restFrequencyInterval, - restFrequencyDate, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy); + restFrequencyDate, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate, + isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy); } final boolean amortization = rs.getBoolean("amortizationBoolean"); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java index 604c34556..f36468cf6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/meeting/api/MeetingsApiResource.java @@ -252,7 +252,7 @@ public class MeetingsApiResource { break; case INVALID: break; - case LOAN_RECALCULATION_DETAIL: + case LOAN_RECALCULATION_REST_DETAIL: break; default: break; diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V258__interest_compounding_changes.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V258__interest_compounding_changes.sql new file mode 100644 index 000000000..8a46dce2d --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V258__interest_compounding_changes.sql @@ -0,0 +1,10 @@ +ALTER TABLE `m_product_loan_recalculation_details` + ADD COLUMN `compounding_frequency_type_enum` SMALLINT(1) NULL DEFAULT NULL, + ADD COLUMN `compounding_frequency_interval` SMALLINT(3) NULL DEFAULT NULL, + ADD COLUMN `compounding_freqency_date` DATE NULL DEFAULT NULL; + + +ALTER TABLE `m_loan_recalculation_details` + ADD COLUMN `compounding_frequency_type_enum` SMALLINT(1) NULL DEFAULT NULL, + ADD COLUMN `compounding_frequency_interval` SMALLINT(3) NULL DEFAULT NULL, + ADD COLUMN `compounding_freqency_date` DATE NULL DEFAULT NULL;
Additional Mandatory Fields if Hold Guarantee funds is enabled(true)