diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java index 0e40d7478..f86bc325d 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/AccountingScenarioIntegrationTest.java @@ -166,8 +166,7 @@ public class AccountingScenarioIntegrationTest { float expected_value = this.LP_PRINCIPAL - PRINCIPAL_VALUE_FOR_EACH_PERIOD; this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, expected_value, loanID); final JournalEntry[] assetAccountFirstEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[1], JournalEntry.TransactionType.DEBIT), - new JournalEntry(FIRST_INTEREST, JournalEntry.TransactionType.CREDIT), - new JournalEntry(FIRST_PRINCIPAL, JournalEntry.TransactionType.CREDIT), }; + new JournalEntry(FIRST_INTEREST + FIRST_PRINCIPAL, JournalEntry.TransactionType.CREDIT) }; this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[1], assetAccountFirstEntry); System.out.println("Repayment 1 Done......"); @@ -179,8 +178,7 @@ public class AccountingScenarioIntegrationTest { expected_value = expected_value - PRINCIPAL_VALUE_FOR_EACH_PERIOD; this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(2, expected_value, loanID); final JournalEntry[] assetAccountSecondEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[2], JournalEntry.TransactionType.DEBIT), - new JournalEntry(SECOND_AND_THIRD_INTEREST, JournalEntry.TransactionType.CREDIT), - new JournalEntry(SECOND_PRINCIPAL, JournalEntry.TransactionType.CREDIT), }; + new JournalEntry(SECOND_AND_THIRD_INTEREST + SECOND_PRINCIPAL, JournalEntry.TransactionType.CREDIT) }; this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[2], assetAccountSecondEntry); System.out.println("Repayment 2 Done ......"); @@ -605,8 +603,7 @@ public class AccountingScenarioIntegrationTest { float expected_value = this.LP_PRINCIPAL - PRINCIPAL_VALUE_FOR_EACH_PERIOD; this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, expected_value, loanID); final JournalEntry[] assetAccountFirstEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[1], JournalEntry.TransactionType.DEBIT), - new JournalEntry(FIRST_INTEREST, JournalEntry.TransactionType.CREDIT), - new JournalEntry(FIRST_PRINCIPAL, JournalEntry.TransactionType.CREDIT), }; + new JournalEntry(FIRST_INTEREST + FIRST_PRINCIPAL, JournalEntry.TransactionType.CREDIT) }; this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[1], assetAccountFirstEntry); System.out.println("Repayment 1 Done......"); @@ -622,8 +619,7 @@ public class AccountingScenarioIntegrationTest { FEE_PORTION, PENALTY_PORTION, loanID); this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(2, expected_value, loanID); final JournalEntry[] assetAccountSecondEntry = { new JournalEntry(this.REPAYMENT_AMOUNT[2], JournalEntry.TransactionType.DEBIT), - new JournalEntry(SECOND_AND_THIRD_INTEREST, JournalEntry.TransactionType.CREDIT), - new JournalEntry(SECOND_PRINCIPAL, JournalEntry.TransactionType.CREDIT), }; + new JournalEntry(SECOND_AND_THIRD_INTEREST + SECOND_PRINCIPAL, JournalEntry.TransactionType.CREDIT) }; this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[2], assetAccountSecondEntry); System.out.println("Repayment 2 Done ......"); @@ -723,7 +719,7 @@ public class AccountingScenarioIntegrationTest { float expected_value = this.LP_PRINCIPAL - PRINCIPAL_VALUE_FOR_EACH_PERIOD; this.loanTransactionHelper.verifyRepaymentScheduleEntryFor(1, expected_value, loanID); final JournalEntry[] assetAccountEntry = { new JournalEntry(15000f, JournalEntry.TransactionType.DEBIT), - new JournalEntry(1000f, JournalEntry.TransactionType.CREDIT), new JournalEntry(10000f, JournalEntry.TransactionType.CREDIT) }; + new JournalEntry(11000f, JournalEntry.TransactionType.CREDIT) }; this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, this.REPAYMENT_DATE[1], assetAccountEntry); this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, this.REPAYMENT_DATE[1], new JournalEntry(4000f, JournalEntry.TransactionType.CREDIT)); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java index 8b520010f..5696143e4 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java @@ -1873,9 +1873,8 @@ public class ClientLoanIntegrationTest { validateCharge(flatInstallmentFee, loanCharges, "50", "150.00", "50.0", "0.0"); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("150.00"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3301.49"), + JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( String.valueOf(flatSpecifiedDueDate), "29 October 2011", "100")); @@ -1899,9 +1898,7 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 2------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3251.49"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"), + new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.CREDIT)); loanSchedule.clear(); @@ -1937,17 +1934,13 @@ public class ClientLoanIntegrationTest { // checking the journal entry as applied penalty has been collected this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("150"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2811.49"), - JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3301.49"), + JournalEntry.TransactionType.CREDIT)); System.out.println("----------Make repayment 3 advance------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.49"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50.00"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3129.11"), + new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( String.valueOf(flatPenaltySpecifiedDueDate), "10 January 2012", "100")); @@ -1970,10 +1963,10 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make over payment for repayment 4 ------------"); this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3220.60"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry( - Float.valueOf("3220.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50.00"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT)); - this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", new JournalEntry(80.92f, + Float.valueOf("3220.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3139.68"), JournalEntry.TransactionType.CREDIT)); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", + new JournalEntry(Float.valueOf("80.92"), JournalEntry.TransactionType.CREDIT)); loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status"); LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap); } @@ -2069,9 +2062,8 @@ public class ClientLoanIntegrationTest { validateCharge(percentageInstallmentFee, loanCharges, "1", "90.89", "29.11", "0.0"); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("149.11"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3300.60"), + JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( String.valueOf(percentageSpecifiedDueDateCharge), "29 October 2011", "1")); @@ -2095,9 +2087,7 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 2------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3271.49"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"), + new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.CREDIT)); loanSchedule.clear(); @@ -2133,17 +2123,13 @@ public class ClientLoanIntegrationTest { // checking the journal entry as applied penalty has been collected this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("149.11"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2791.49"), - JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3300.60"), + JournalEntry.TransactionType.CREDIT)); System.out.println("----------Make repayment 3 advance------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.78"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("30.29"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3149.11"), + new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( String.valueOf(percentagePenaltySpecifiedDueDate), "10 January 2012", "1")); @@ -2166,10 +2152,10 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make over payment for repayment 4 ------------"); this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3220.58"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry( - Float.valueOf("3220.58"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("30.90"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT)); - this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", new JournalEntry(100f, + Float.valueOf("3220.58"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3120.58"), JournalEntry.TransactionType.CREDIT)); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", + new JournalEntry(Float.valueOf("100.00"), JournalEntry.TransactionType.CREDIT)); loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status"); LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap); } @@ -2274,9 +2260,8 @@ public class ClientLoanIntegrationTest { validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "94.53", "31.51", "0.0"); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("157.57"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3309.06"), + JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan( loanID, @@ -2303,9 +2288,7 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 2------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3277.55"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("126.06"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"), + new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.CREDIT)); loanSchedule.clear(); @@ -2343,17 +2326,13 @@ public class ClientLoanIntegrationTest { // checking the journal entry as applied penalty has been collected this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("157.57"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2791.49"), - JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3309.06"), + JournalEntry.TransactionType.CREDIT)); System.out.println("----------Make repayment 3 advance------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3303"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", new JournalEntry(Float.valueOf("3303"), - JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3149.11"), - JournalEntry.TransactionType.CREDIT)); + JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3303"), JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan( loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( @@ -2377,9 +2356,9 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make over payment for repayment 4 ------------"); this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3221.61"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry( - Float.valueOf("3221.61"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("31.51"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT)); - this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", new JournalEntry(100.42f, + Float.valueOf("3221.61"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3121.19"), + JournalEntry.TransactionType.CREDIT)); + this.journalEntryHelper.checkJournalEntryForLiabilityAccount(overpaymentAccount, "20 January 2012", new JournalEntry(Float.valueOf("100.42"), JournalEntry.TransactionType.CREDIT)); loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status"); LoanStatusChecker.verifyLoanAccountIsOverPaid(loanStatusHashMap); @@ -2468,9 +2447,8 @@ public class ClientLoanIntegrationTest { validateCharge(flatInstallmentFee, loanCharges, "50", "150.00", "50.0", "0.0"); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("150.00"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3301.49"), + JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( String.valueOf(flatSpecifiedDueDate), "29 October 2011", "100")); @@ -2509,9 +2487,7 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 2------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3251.49"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100.00"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"), + new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3251.49"), JournalEntry.TransactionType.CREDIT)); loanSchedule.clear(); @@ -2547,17 +2523,13 @@ public class ClientLoanIntegrationTest { // checking the journal entry as applied penalty has been collected this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("150"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2811.49"), - JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3301.49"), + JournalEntry.TransactionType.CREDIT)); System.out.println("----------Make repayment 3 advance------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.49"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3129.11"), + new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3301.49"), JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( @@ -2581,8 +2553,8 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 4 ------------"); this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3139.68"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry( - Float.valueOf("3139.68"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50.00"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3139.68"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3139.68"), + JournalEntry.TransactionType.CREDIT)); loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status"); LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); } @@ -2673,9 +2645,8 @@ public class ClientLoanIntegrationTest { validateCharge(percentageInstallmentFee, loanCharges, "1", "90.89", "29.11", "0.0"); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("149.11"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3300.60"), + JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( String.valueOf(percentageSpecifiedDueDateCharge), "29 October 2011", "1")); @@ -2715,9 +2686,7 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 2------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3271.49"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"), + new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3271.49"), JournalEntry.TransactionType.CREDIT)); loanSchedule.clear(); @@ -2753,17 +2722,13 @@ public class ClientLoanIntegrationTest { // checking the journal entry as applied penalty has been collected this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("149.11"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2791.49"), - JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3300.60"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3300.60"), + JournalEntry.TransactionType.CREDIT)); System.out.println("----------Make repayment 3 advance------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3301.78"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("30.29"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3149.11"), + new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3301.78"), JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan(loanID, LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON( @@ -2787,8 +2752,8 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 4 ------------"); this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3120.58"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry( - Float.valueOf("3120.58"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("30.90"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3120.58"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3120.58"), + JournalEntry.TransactionType.CREDIT)); loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status"); LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); } @@ -2880,9 +2845,8 @@ public class ClientLoanIntegrationTest { validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "94.53", "31.51", "0.0"); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("157.57"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("2911.49"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3309.06"), + JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan( loanID, @@ -2925,9 +2889,7 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 2------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3277.55"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", - new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("126.06"), - JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("181.77"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2969.72"), + new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3277.55"), JournalEntry.TransactionType.CREDIT)); loanSchedule.clear(); @@ -2965,17 +2927,13 @@ public class ClientLoanIntegrationTest { // checking the journal entry as applied penalty has been collected this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 October 2011", new JournalEntry( - Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("120"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("157.57"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2791.49"), - JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3309.06"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3309.06"), + JournalEntry.TransactionType.CREDIT)); System.out.println("----------Make repayment 3 advance------------"); this.loanTransactionHelper.makeRepayment("20 November 2011", Float.valueOf("3303"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 November 2011", new JournalEntry(Float.valueOf("3303"), - JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("122.38"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3149.11"), - JournalEntry.TransactionType.CREDIT)); + JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3303"), JournalEntry.TransactionType.CREDIT)); this.loanTransactionHelper.addChargesForLoan( loanID, @@ -3000,8 +2958,8 @@ public class ClientLoanIntegrationTest { System.out.println("----------Make repayment 4 ------------"); this.loanTransactionHelper.makeRepayment("20 January 2012", Float.valueOf("3121.19"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 January 2012", new JournalEntry( - Float.valueOf("3121.19"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("31.51"), - JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("3089.68"), JournalEntry.TransactionType.CREDIT)); + Float.valueOf("3121.19"), JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("3121.19"), + JournalEntry.TransactionType.CREDIT)); loanStatusHashMap = (HashMap) this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "status"); LoanStatusChecker.verifyLoanAccountIsClosed(loanStatusHashMap); } @@ -3198,7 +3156,7 @@ public class ClientLoanIntegrationTest { LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, - LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null); + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null, LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList(0)); @@ -3305,7 +3263,8 @@ public class ClientLoanIntegrationTest { final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.DEFAULT_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, - LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, preCloseInterestStrategy, null); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, preCloseInterestStrategy, null, + null, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null, LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList(0)); @@ -3384,7 +3343,7 @@ public class ClientLoanIntegrationTest { LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, - LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null); + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null, null); List charges = new ArrayList<>(); Integer installmentCharge = ChargesHelper.createCharges(requestSpec, responseSpec, @@ -3473,6 +3432,8 @@ public class ClientLoanIntegrationTest { Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); todaysDate.add(Calendar.DAY_OF_MONTH, -14); final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime()); + Integer dayOfMonth = getDayOfMonth(todaysDate); + Integer dayOfWeek = getDayOfWeek(todaysDate); final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); @@ -3481,10 +3442,10 @@ public class ClientLoanIntegrationTest { 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); + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null, dayOfWeek, null, dayOfWeek); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, - LOAN_DISBURSEMENT_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0)); + LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0)); Assert.assertNotNull(loanID); HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); @@ -3560,8 +3521,8 @@ public class ClientLoanIntegrationTest { paymentday.put("penaltyChargesDue", "0"); addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "0.0", "0.0"); expectedvalues.add(paymentday); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.28", "11.63", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "1009.87", "4.66", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.31", "11.6", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "1009.84", "4.66", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); @@ -3583,8 +3544,12 @@ public class ClientLoanIntegrationTest { Calendar todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); todaysDate.add(Calendar.DAY_OF_MONTH, -14); + Integer compoundingDayOfMonth = getDayOfMonth(todaysDate); + Integer compoundingDayOfWeek = getDayOfWeek(todaysDate); final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime()); todaysDate.add(Calendar.DAY_OF_MONTH, -2); + Integer restDayOfMonth = getDayOfMonth(todaysDate); + Integer restDayOfWeek = getDayOfWeek(todaysDate); final String REST_START_DATE = dateFormat.format(todaysDate.getTime()); todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); @@ -3609,10 +3574,11 @@ public class ClientLoanIntegrationTest { 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); + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, compoundingDayOfMonth, compoundingDayOfWeek, + restDayOfMonth, restDayOfWeek); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, - REST_START_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, charges); + LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, charges); Assert.assertNotNull(loanID); HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); @@ -3622,9 +3588,9 @@ public class ClientLoanIntegrationTest { List> 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, "2486.03", "42.88", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.5", "31.41", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2533.71", "19.93", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------"); @@ -3641,8 +3607,8 @@ public class ClientLoanIntegrationTest { todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.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"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.67", "23.24", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2529.49", "11.67", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); @@ -3656,9 +3622,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, "2486.03", "42.88", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.5", "31.41", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2533.71", "19.93", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); Float earlyPayment = new Float("5100"); @@ -3671,9 +3637,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, "5100.0", "36.16", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "0", "11.16", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2417.24", "11.16", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "5065.31", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "0", "11.32", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2451.93", "11.32", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); @@ -3689,7 +3655,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.82"; + String preCloseAmount = "7761.89"; testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST( preCloseInterestStrategy, preCloseAmount); @@ -3698,7 +3664,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.8"; + String preCloseAmount = "7786.79"; testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST( preCloseInterestStrategy, preCloseAmount); @@ -3715,7 +3681,9 @@ public class ClientLoanIntegrationTest { todaysDate.add(Calendar.DAY_OF_MONTH, -16); final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime()); todaysDate.add(Calendar.DAY_OF_MONTH, -4); - final String REST_START_DATE = dateFormat.format(todaysDate.getTime()); + Integer restDateOfMonth = getDayOfMonth(todaysDate); + Integer restDateOfWeek = getDayOfWeek(todaysDate); + final String REST_START_DATE = null; todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); todaysDate.add(Calendar.DAY_OF_MONTH, -16); @@ -3738,7 +3706,8 @@ public class ClientLoanIntegrationTest { LoanProductTestBuilder.DEFAULT_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_SAME_AS_REPAYMENT_PERIOD, null, null, preCloseInterestStrategy, null); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, null, null, preCloseInterestStrategy, null, + null, null, restDateOfMonth, restDateOfWeek); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, REST_START_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, charges); @@ -3751,9 +3720,9 @@ public class ClientLoanIntegrationTest { List> 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, "2489.3", "39.61", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2500.78", "28.13", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.16", "16.62", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2505.73", "23.18", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------"); @@ -3770,8 +3739,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, "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"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2481.87", "47.04", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2553.29", "11.78", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); @@ -3785,9 +3754,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, "2489.3", "39.61", "0.0", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2500.7", "28.21", "200", "0.0"); - addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.24", "16.62", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.22", "34.69", "0.0", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2494.05", "34.86", "200", "0.0"); + addRepaymentValues(expectedvalues, todaysDate, 1, false, "2528.97", "11.67", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID); @@ -3831,7 +3800,7 @@ public class ClientLoanIntegrationTest { LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", REST_START_DATE, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, - overdueFeeChargeId.toString(), false); + overdueFeeChargeId.toString(), false, null, null, null, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, REST_START_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, null); @@ -3940,7 +3909,7 @@ public class ClientLoanIntegrationTest { LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, - LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, accounts); + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, accounts, null, null); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null, LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList(0)); @@ -3983,6 +3952,7 @@ public class ClientLoanIntegrationTest { this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, LOAN_DISBURSEMENT_DATE, assetAccountInitialEntry); todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); String runOndate = dateFormat.format(todaysDate.getTime()); + System.out.println("runOndate : " + runOndate); this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOndate); this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant().minusDays(7), 46.15f, 0f, 0f, loanID); this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant(), 46.15f, 0f, 0f, loanID); @@ -4002,6 +3972,7 @@ public class ClientLoanIntegrationTest { addRepaymentValues(expectedvalues, todaysDate, 1, false, "2517.29", "11.62", "0.0", "0.0"); verifyLoanRepaymentSchedule(loanSchedule, expectedvalues); + this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(runOndate); this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant().minusDays(7), 46.15f, 0f, 0f, loanID); this.loanTransactionHelper.checkAccrualTransactionForRepayment(Utils.getLocalDateOfTenant(), 34.69f, 0f, 0f, loanID); @@ -4036,7 +4007,8 @@ public class ClientLoanIntegrationTest { 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); + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, getDayOfMonth(todaysDate), + getDayOfWeek(todaysDate), getDayOfMonth(todaysDate), getDayOfWeek(todaysDate)); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0)); @@ -4119,7 +4091,7 @@ public class ClientLoanIntegrationTest { LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate, LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, null, - true); + true, null, null, getDayOfMonth(todaysDate), getDayOfWeek(todaysDate)); final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0)); @@ -4206,10 +4178,10 @@ public class ClientLoanIntegrationTest { final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.DEFAULT_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, - LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, preCloseStrategy, null); + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, preCloseStrategy, null, null, null); final Integer loanID = applyForLoanApplicationForInterestRecalculationWithMoratorium(clientID, loanProductID, - LOAN_DISBURSEMENT_DATE, null, LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList(0), "1", null); + LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList(0), "1", null); Assert.assertNotNull(loanID); HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); @@ -4284,14 +4256,19 @@ public class ClientLoanIntegrationTest { 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 recalculationRestFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts, + final Integer recalculationRestFrequencyOnDayType, final Integer recalculationRestFrequencyDayOfWeekType) { final String recalculationCompoundingFrequencyType = null; final String recalculationCompoundingFrequencyInterval = null; final String recalculationCompoundingFrequencyDate = null; + final Integer recalculationCompoundingFrequencyOnDayType = null; + final Integer recalculationCompoundingFrequencyDayOfWeekType = null; return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, - recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false); + recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false, + recalculationCompoundingFrequencyOnDayType, recalculationCompoundingFrequencyDayOfWeekType, + recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType); } private Integer createLoanProductWithInterestRecalculationAndCompoundingDetails(final String repaymentStrategy, @@ -4299,11 +4276,15 @@ public class ClientLoanIntegrationTest { final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType, final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate, - final String preCloseInterestCalculationStrategy, final Account[] accounts) { + final String preCloseInterestCalculationStrategy, final Account[] accounts, + final Integer recalculationCompoundingFrequencyOnDayType, final Integer recalculationCompoundingFrequencyDayOfWeekType, + final Integer recalculationRestFrequencyOnDayType, final Integer recalculationRestFrequencyDayOfWeekType) { return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, - recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false); + recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false, + recalculationCompoundingFrequencyOnDayType, recalculationCompoundingFrequencyDayOfWeekType, + recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType); } private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy, @@ -4312,7 +4293,9 @@ public class ClientLoanIntegrationTest { final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType, final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId, - boolean isArrearsBasedOnOriginalSchedule) { + boolean isArrearsBasedOnOriginalSchedule, final Integer recalculationCompoundingFrequencyOnDayType, + final Integer recalculationCompoundingFrequencyDayOfWeekType, final Integer recalculationRestFrequencyOnDayType, + final Integer recalculationRestFrequencyDayOfWeekType) { System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); LoanProductTestBuilder builder = new LoanProductTestBuilder() .withPrincipal("10000000.00") @@ -4328,9 +4311,10 @@ public class ClientLoanIntegrationTest { .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, preCloseInterestCalculationStrategy) .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval, - recalculationRestFrequencyDate) + recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType) .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType, - recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate); + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType, + recalculationCompoundingFrequencyDayOfWeekType); if (accounts != null) { builder = builder.withAccountingRulePeriodicAccrual(accounts); } @@ -4342,30 +4326,27 @@ 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); + final String disbursementDate, final String repaymentStrategy, final List charges) { + return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, repaymentStrategy, charges, null, + null); } private Integer applyForLoanApplicationForInterestRecalculationWithMoratorium(final Integer clientID, final Integer loanProductID, - final String disbursementDate, final String restStartDate, final String repaymentStrategy, final List charges, + final String disbursementDate, final String repaymentStrategy, final List charges, final String graceOnInterestPayment, final String graceOnPrincipalPayment) { - final String compoundingStartDate = null; - return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate, - compoundingStartDate, repaymentStrategy, charges, graceOnInterestPayment, graceOnPrincipalPayment); + return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, repaymentStrategy, charges, + graceOnInterestPayment, graceOnPrincipalPayment); } private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, - final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy, - final List charges) { - return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate, - compoundingStartDate, repaymentStrategy, charges, null, null); + final String disbursementDate, final String compoundingStartDate, final String repaymentStrategy, final List charges) { + return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, repaymentStrategy, charges, null, + null); } private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, - final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy, - final List charges, final String graceOnInterestPayment, final String graceOnPrincipalPayment) { + final String disbursementDate, final String repaymentStrategy, final List charges, + final String graceOnInterestPayment, final String graceOnPrincipalPayment) { System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); final String loanApplicationJSON = new LoanApplicationTestBuilder() // .withPrincipal("10000.00") // @@ -4380,8 +4361,6 @@ public class ClientLoanIntegrationTest { .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // .withExpectedDisbursementDate(disbursementDate) // .withSubmittedOnDate(disbursementDate) // - .withRestFrequencyDate(restStartDate)// - .withCompoundingFrequencyDate(compoundingStartDate)// .withwithRepaymentStrategy(repaymentStrategy) // .withPrincipalGrace(graceOnPrincipalPayment) // .withInterestGrace(graceOnInterestPayment)// @@ -4593,9 +4572,9 @@ public class ClientLoanIntegrationTest { final Account expenseAccount = this.accountHelper.createExpenseAccount(); final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); - final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.DEFAULT_STRATEGY, ACCRUAL_UPFRONT, - assetAccount, incomeAccount, expenseAccount, overpaymentAccount);// , - // LoanProductTestBuilder.EQUAL_INSTALLMENTS, + final Integer loanProductID = createLoanProduct("0", "0", LoanProductTestBuilder.DEFAULT_STRATEGY, ACCRUAL_UPFRONT, assetAccount, + incomeAccount, expenseAccount, overpaymentAccount);// , + // LoanProductTestBuilder.EQUAL_INSTALLMENTS, // LoanProductTestBuilder.FLAT_BALANCE); Assert.assertNotNull(loanProductID); @@ -4656,9 +4635,7 @@ public class ClientLoanIntegrationTest { this.loanTransactionHelper.makeRepayment(twoMonthsfromNow, Float.valueOf("2290"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, twoMonthsfromNow, new JournalEntry(Float.valueOf("2290"), - JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("50"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("240"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("2000"), - JournalEntry.TransactionType.CREDIT)); + JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("2290"), JournalEntry.TransactionType.CREDIT)); loanSchedule.clear(); loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); @@ -4673,9 +4650,7 @@ public class ClientLoanIntegrationTest { this.loanTransactionHelper.makeRepayment(oneMonthfromNow, Float.valueOf("4580"), loanID); this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, oneMonthfromNow, new JournalEntry(Float.valueOf("4580"), - JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("100"), JournalEntry.TransactionType.CREDIT), - new JournalEntry(Float.valueOf("480"), JournalEntry.TransactionType.CREDIT), new JournalEntry(Float.valueOf("4000"), - JournalEntry.TransactionType.CREDIT)); + JournalEntry.TransactionType.DEBIT), new JournalEntry(Float.valueOf("4580"), JournalEntry.TransactionType.CREDIT)); loanSchedule.clear(); loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID); @@ -4967,10 +4942,9 @@ public class ClientLoanIntegrationTest { System.out.println("------------------------LOAN CREATED WITH ID------------------------------" + loanID); loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments() - .withRepaymentTypeAsMonth().withRepaymentAfterEvery("1") - .withRepaymentStrategy(LoanProductTestBuilder.DEFAULT_STRATEGY).withInterestTypeAsDecliningBalance() - .withInterestCalculationPeriodTypeAsDays().withInArrearsTolerance("10").withMoratorium("2", "3") - .withLoanProductConfiguration(loanProductConfigurationAsFalse).build(null)); + .withRepaymentTypeAsMonth().withRepaymentAfterEvery("1").withRepaymentStrategy(LoanProductTestBuilder.DEFAULT_STRATEGY) + .withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeAsDays().withInArrearsTolerance("10") + .withMoratorium("2", "3").withLoanProductConfiguration(loanProductConfigurationAsFalse).build(null)); System.out.println("-------------------LOAN PRODUCT CREATED WITH ATTRIBUTE CONFIGURATION AS FALSE----------------------" + loanProductID); /* @@ -4990,8 +4964,8 @@ public class ClientLoanIntegrationTest { "inArrearsTolerance", "graceOnArrearsAgeing"); for (String comparisonAttribute : comparisonAttributes) { - Object val1 = JsonPath.from(loanProductDetails).get(comparisonAttribute); - Object val2 = JsonPath.from(loanDetails).get(comparisonAttribute); + Object val1 = JsonPath.from(loanProductDetails).get(comparisonAttribute); + Object val2 = JsonPath.from(loanDetails).get(comparisonAttribute); assertEquals(val1, val2); } } @@ -5050,4 +5024,26 @@ public class ClientLoanIntegrationTest { return this.loanTransactionHelper.getLoanId(loanApplicationJSON); } + + public Integer getDayOfWeek(Calendar date) { + Integer dayOfWeek = null; + if (null != date) { + dayOfWeek = date.get(Calendar.DAY_OF_WEEK) - 1; + if (dayOfWeek.compareTo(0) == 0) { + dayOfWeek = 7; + } + } + return dayOfWeek; + } + + public Integer getDayOfMonth(Calendar date) { + Integer dayOfMonth = null; + if (null != date) { + dayOfMonth = date.get(Calendar.DAY_OF_MONTH); + if (dayOfMonth.compareTo(28) > 0) { + dayOfMonth = 28; + } + } + return dayOfMonth; + } } \ No newline at end of file diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java index d0018cd2b..b56f25dd2 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanRepaymentRescheduleAtDisbursementTest.java @@ -72,7 +72,6 @@ public class LoanRepaymentRescheduleAtDisbursementTest { final String expectedDisbursementDate = "01 March 2015"; final String disbursementDate = "01 March 2015"; final String adjustRepaymentDate = "16 March 2015"; - final String recalculationRestFrequencyDate = "01 January 2012"; // CREATE CLIENT final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2014"); @@ -83,7 +82,7 @@ public class LoanRepaymentRescheduleAtDisbursementTest { final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, - LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "0", recalculationRestFrequencyDate, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "0", LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null); // CREATE TRANCHES @@ -98,7 +97,7 @@ public class LoanRepaymentRescheduleAtDisbursementTest { // APPLY FOR TRANCHE LOAN WITH INTEREST RECALCULATION final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, - recalculationRestFrequencyDate, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0), createTranches); + LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList(0), createTranches); HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); @@ -148,23 +147,28 @@ public class LoanRepaymentRescheduleAtDisbursementTest { 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 preCloseInterestCalculationStrategy, final Account[] accounts) { final String recalculationCompoundingFrequencyType = null; final String recalculationCompoundingFrequencyInterval = null; - final String recalculationCompoundingFrequencyDate = null; + final Integer recalculationCompoundingFrequencyOnDayType = null; + final Integer recalculationCompoundingFrequencyDayOfWeekType = null; + final Integer recalculationRestFrequencyOnDayType = null; + final Integer recalculationRestFrequencyDayOfWeekType = null; return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, - recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, - recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false); + recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, preCloseInterestCalculationStrategy, + accounts, null, false, recalculationCompoundingFrequencyOnDayType, recalculationCompoundingFrequencyDayOfWeekType, + recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType); } private Integer createLoanProductWithInterestRecalculation(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 recalculationCompoundingFrequencyType, final String recalculationCompoundingFrequencyInterval, final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId, - boolean isArrearsBasedOnOriginalSchedule) { + boolean isArrearsBasedOnOriginalSchedule, final Integer recalculationCompoundingFrequencyOnDayType, + final Integer recalculationCompoundingFrequencyDayOfWeekType, final Integer recalculationRestFrequencyOnDayType, + final Integer recalculationRestFrequencyDayOfWeekType) { System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); LoanProductTestBuilder builder = new LoanProductTestBuilder() .withPrincipal("10000.00") @@ -180,9 +184,10 @@ public class LoanRepaymentRescheduleAtDisbursementTest { .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, preCloseInterestCalculationStrategy) .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval, - recalculationRestFrequencyDate) + recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType) .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType, - recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate); + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType, + recalculationCompoundingFrequencyDayOfWeekType); if (accounts != null) { builder = builder.withAccountingRulePeriodicAccrual(accounts); } @@ -194,17 +199,16 @@ public class LoanRepaymentRescheduleAtDisbursementTest { } private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, - final String disbursementDate, final String restStartDate, final String repaymentStrategy, final List charges, List tranches) { + final String disbursementDate, final String repaymentStrategy, final List charges, List tranches) { final String graceOnInterestPayment = null; - final String compoundingStartDate = null; final String graceOnPrincipalPayment = null; - return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate, - compoundingStartDate, repaymentStrategy, charges, graceOnInterestPayment, graceOnPrincipalPayment,tranches); + return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, repaymentStrategy, charges, + graceOnInterestPayment, graceOnPrincipalPayment, tranches); } private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, final Integer loanProductID, - final String disbursementDate, final String restStartDate, final String compoundingStartDate, final String repaymentStrategy, - final List charges, final String graceOnInterestPayment, final String graceOnPrincipalPayment, List tranches) { + final String disbursementDate, final String repaymentStrategy, final List charges, final String graceOnInterestPayment, + final String graceOnPrincipalPayment, List tranches) { System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); final String loanApplicationJSON = new LoanApplicationTestBuilder() // .withPrincipal("10000.00") // @@ -222,7 +226,6 @@ public class LoanRepaymentRescheduleAtDisbursementTest { .withInterestCalculationPeriodTypeAsDays() // .withExpectedDisbursementDate(disbursementDate) // .withSubmittedOnDate(disbursementDate) // - .withRestFrequencyDate(restStartDate)// .withwithRepaymentStrategy(repaymentStrategy) // .withCharges(charges)// .build(clientID.toString(), loanProductID.toString(), null); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java index a825bcb05..4126f75c7 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java @@ -111,7 +111,8 @@ public class LoanReschedulingWithinCenterTest { LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "0", recalculationRestFrequencyDate, - LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, isMultiTrancheLoan); + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, isMultiTrancheLoan, + null, null); // APPLY FOR TRANCHE LOAN WITH INTEREST RECALCULATION final Integer loanId = applyForLoanApplicationForInterestRecalculation(clientId, groupId, calendarId, loanProductID, disbursalDate, @@ -222,7 +223,8 @@ public class LoanReschedulingWithinCenterTest { LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "0", recalculationRestFrequencyDate, - LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, isMultiTrancheLoan); + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, isMultiTrancheLoan, + null, null); // CREATE TRANCHES List createTranches = new ArrayList<>(); @@ -279,14 +281,18 @@ public class LoanReschedulingWithinCenterTest { final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, final String recalculationRestFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts, - final boolean isMultiTrancheLoan) { + final boolean isMultiTrancheLoan, final Integer recalculationRestFrequencyOnDayType, final Integer recalculationRestFrequencyDayOfWeekType) { final String recalculationCompoundingFrequencyType = null; final String recalculationCompoundingFrequencyInterval = null; final String recalculationCompoundingFrequencyDate = null; + final Integer recalculationCompoundingFrequencyOnDayType = null; + final Integer recalculationCompoundingFrequencyDayOfWeekType = null; return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, - recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false, isMultiTrancheLoan); + recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false, isMultiTrancheLoan, + recalculationCompoundingFrequencyOnDayType, recalculationCompoundingFrequencyDayOfWeekType, recalculationRestFrequencyOnDayType, + recalculationRestFrequencyDayOfWeekType); } private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy, @@ -295,7 +301,10 @@ public class LoanReschedulingWithinCenterTest { final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType, final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId, - boolean isArrearsBasedOnOriginalSchedule, final boolean isMultiTrancheLoan) { + boolean isArrearsBasedOnOriginalSchedule, final boolean isMultiTrancheLoan, + final Integer recalculationCompoundingFrequencyOnDayType, + final Integer recalculationCompoundingFrequencyDayOfWeekType, final Integer recalculationRestFrequencyOnDayType, + final Integer recalculationRestFrequencyDayOfWeekType) { System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); LoanProductTestBuilder builder = new LoanProductTestBuilder() .withPrincipal("10000.00") @@ -311,9 +320,10 @@ public class LoanReschedulingWithinCenterTest { .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, preCloseInterestCalculationStrategy) .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval, - recalculationRestFrequencyDate) + recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType) .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType, - recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate); + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType, + recalculationCompoundingFrequencyDayOfWeekType); if (accounts != null) { builder = builder.withAccountingRulePeriodicAccrual(accounts); } @@ -356,7 +366,6 @@ public class LoanReschedulingWithinCenterTest { .withInterestCalculationPeriodTypeAsDays() // .withExpectedDisbursementDate(disbursementDate) // .withSubmittedOnDate(disbursementDate) // - .withRestFrequencyDate(restStartDate)// .withwithRepaymentStrategy(repaymentStrategy) // .withCharges(charges)// .build(clientID.toString(), groupId.toString(), loanProductID.toString(), null); @@ -389,4 +398,5 @@ public class LoanReschedulingWithinCenterTest { date.add(type, addvalue); return new ArrayList<>(Arrays.asList(date.get(Calendar.YEAR), date.get(Calendar.MONTH) + 1, date.get(Calendar.DAY_OF_MONTH))); } + } \ No newline at end of file diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java index cfa9c39f3..50d9cc98c 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -61,8 +61,6 @@ public class LoanApplicationTestBuilder { private List disbursementData = null; @SuppressWarnings("rawtypes") private List charges = new ArrayList<>(); - private String recalculationRestFrequencyDate = null; - private String recalculationCompoundingFrequencyDate = null; private String repaymentsStartingFromDate = null; private String calendarId; @@ -135,12 +133,6 @@ public class LoanApplicationTestBuilder { map.put("maxOutstandingLoanBalance", maxOutstandingLoanBalance); } - 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); @@ -281,15 +273,7 @@ public class LoanApplicationTestBuilder { return this; } - public LoanApplicationTestBuilder withRestFrequencyDate(final String recalculationRestFrequencyDate) { - 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; diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java index 3b4c76768..5f90468df 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/loans/LoanProductTestBuilder.java @@ -104,10 +104,8 @@ public class LoanProductTestBuilder { private String rescheduleStrategyMethod = "1"; 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; @@ -122,6 +120,10 @@ public class LoanProductTestBuilder { private Boolean allowVariableInstallments = Boolean.FALSE; private Integer minimumGap; private Integer maximumGap; + private Integer recalculationCompoundingFrequencyOnDayType = null; + private Integer recalculationRestFrequencyOnDayType = null; + private Integer recalculationCompoundingFrequencyDayOfWeekType = null; + private Integer recalculationRestFrequencyDayOfWeekType = null; public String build(final String chargeId) { final HashMap map = new HashMap<>(); @@ -177,16 +179,18 @@ public class LoanProductTestBuilder { map.put("rescheduleStrategyMethod", this.rescheduleStrategyMethod); 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); } + map.put("recalculationCompoundingFrequencyOnDayType", this.recalculationCompoundingFrequencyOnDayType); + map.put("recalculationCompoundingFrequencyDayOfWeekType", this.recalculationCompoundingFrequencyDayOfWeekType); + map.put("recalculationRestFrequencyOnDayType", this.recalculationRestFrequencyOnDayType); + map.put("recalculationRestFrequencyDayOfWeekType", this.recalculationRestFrequencyDayOfWeekType); } if (holdGuaranteeFunds != null) { map.put("holdGuaranteeFunds", this.holdGuaranteeFunds); @@ -437,20 +441,24 @@ public class LoanProductTestBuilder { } public LoanProductTestBuilder withInterestRecalculationRestFrequencyDetails(final String recalculationRestFrequencyType, - final String recalculationRestFrequencyInterval, final String recalculationRestFrequencyDate) { + final String recalculationRestFrequencyInterval, final Integer recalculationRestFrequencyOnDayType, + final Integer recalculationRestFrequencyDayOfWeekType) { this.isInterestRecalculationEnabled = true; this.recalculationRestFrequencyType = recalculationRestFrequencyType; this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval; - this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; + this.recalculationRestFrequencyOnDayType = recalculationRestFrequencyOnDayType; + this.recalculationRestFrequencyDayOfWeekType = recalculationRestFrequencyDayOfWeekType; return this; } public LoanProductTestBuilder withInterestRecalculationCompoundingFrequencyDetails(final String recalculationCompoundingFrequencyType, - final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate) { + final String recalculationCompoundingFrequencyInterval, final Integer recalculationCompoundingFrequencyOnDayType, + final Integer recalculationCompoundingFrequencyDayOfWeekType) { this.isInterestRecalculationEnabled = true; this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType; this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval; - this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate; + this.recalculationCompoundingFrequencyOnDayType = recalculationCompoundingFrequencyOnDayType; + this.recalculationCompoundingFrequencyDayOfWeekType = recalculationCompoundingFrequencyDayOfWeekType; return this; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java index 3e499d1f1..447cb13a1 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntry.java @@ -173,6 +173,9 @@ public class JournalEntry extends AbstractAuditableCustom { return this.amount; } + public void setAmount(BigDecimal amount) { + this.amount = amount; + } public void setReversalJournalEntry(final JournalEntry reversalJournalEntry) { this.reversalJournalEntry = reversalJournalEntry; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java index ab61a707d..120b03e7f 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/domain/JournalEntryRepository.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.accounting.journalentry.domain; +import java.util.Date; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; @@ -39,8 +40,13 @@ public interface JournalEntryRepository extends JpaRepository findProvisioningJournalEntriesByEntityId(@Param("entityId") Long entityId, @Param("entityType") Integer entityType) ; - + @Query("from JournalEntry journalEntry where journalEntry.transactionId= :transactionId and journalEntry.reversed is false and journalEntry.entityType = :entityType") - List findJournalEntries(@Param("transactionId") String transactionId, @Param("entityType") Integer entityType) ; - + List findJournalEntries(@Param("transactionId") String transactionId, @Param("entityType") Integer entityType); + + @Query("from JournalEntry journalEntry where glAccount.id= :accountId and transactionId= :transactionId and transactionDate= :transactionDate" + + " and type= :journalEntryType and entityType=1 and entityId= :loanId and loanTransaction.id= :loanTransactionId") + JournalEntry findLOANJournalEntryWith(@Param("accountId") Long accountId, @Param("transactionId") String transactionId, + @Param("transactionDate") Date transactionDate, @Param("journalEntryType") Integer journalEntryType, + @Param("loanId") Long loanId, @Param("loanTransactionId") Long loanTransactionId); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java index 3d64581f6..c7a0c2396 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java @@ -781,6 +781,12 @@ public class AccountingProcessorHelper { final Long loanProductId, final Long paymentTypeId, final Long loanId, final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal) { final GLAccount account = getLinkedGLAccountForLoanProduct(loanProductId, accountMappingTypeId, paymentTypeId); + createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate, amount, isReversal, account); + } + + public void createCreditJournalEntryOrReversalForLoan(final Office office, final String currencyCode, final Long loanId, + final String transactionId, final Date transactionDate, final BigDecimal amount, final Boolean isReversal, + final GLAccount account) { if (isReversal) { createDebitJournalEntryForLoan(office, currencyCode, account, loanId, transactionId, transactionDate, amount); } else { @@ -1070,7 +1076,7 @@ public class AccountingProcessorHelper { this.glJournalEntryRepository.save(journalEntry); } - private GLAccount getLinkedGLAccountForLoanProduct(final Long loanProductId, final int accountMappingTypeId, final Long paymentTypeId) { + public GLAccount getLinkedGLAccountForLoanProduct(final Long loanProductId, final int accountMappingTypeId, final Long paymentTypeId) { GLAccount glAccount = null; if (isOrganizationAccount(accountMappingTypeId)) { FinancialActivityAccount financialActivityAccount = this.financialActivityAccountRepository diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java index 8287b7252..7408ac9ce 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java @@ -21,12 +21,16 @@ package org.apache.fineract.accounting.journalentry.service; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import org.apache.fineract.accounting.closure.domain.GLClosure; import org.apache.fineract.accounting.common.AccountingConstants.ACCRUAL_ACCOUNTS_FOR_LOAN; import org.apache.fineract.accounting.common.AccountingConstants.CASH_ACCOUNTS_FOR_LOAN; import org.apache.fineract.accounting.common.AccountingConstants.FINANCIAL_ACTIVITY; +import org.apache.fineract.accounting.glaccount.domain.GLAccount; import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO; import org.apache.fineract.accounting.journalentry.data.LoanDTO; import org.apache.fineract.accounting.journalentry.data.LoanTransactionDTO; @@ -88,7 +92,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess .getTransactionType().isWaiveCharges())) { createJournalEntriesForRepaymentsAndWriteOffs(loanDTO, loanTransactionDTO, office, true, false); } - + /** Logic for Refunds of Active Loans **/ else if (loanTransactionDTO.getTransactionType().isRefundForActiveLoans()) { createJournalEntriesForRefundForActiveLoan(loanDTO, loanTransactionDTO, office); @@ -193,18 +197,27 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess BigDecimal totalDebitAmount = new BigDecimal(0); + Map accountMap = new HashMap<>(); + // handle principal payment or writeOff (and reversals) if (principalAmount != null && !(principalAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(principalAmount); - this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO, - loanProductId, paymentTypeId, loanId, transactionId, transactionDate, principalAmount, isReversal); + GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.LOAN_PORTFOLIO.getValue(), paymentTypeId); + accountMap.put(account, principalAmount); } // handle interest payment of writeOff (and reversals) if (interestAmount != null && !(interestAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(interestAmount); - this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE, - loanProductId, paymentTypeId, loanId, transactionId, transactionDate, interestAmount, isReversal); + GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INTEREST_RECEIVABLE.getValue(), paymentTypeId); + if (accountMap.containsKey(account)) { + BigDecimal amount = accountMap.get(account).add(interestAmount); + accountMap.put(account, amount); + } else { + accountMap.put(account, interestAmount); + } } // handle fees payment of writeOff (and reversals) @@ -212,12 +225,23 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess totalDebitAmount = totalDebitAmount.add(feesAmount); if (isIncomeFromFee) { - this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode, - ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, - feesAmount, isReversal, loanTransactionDTO.getFeePayments()); + GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), paymentTypeId); + if (accountMap.containsKey(account)) { + BigDecimal amount = accountMap.get(account).add(feesAmount); + accountMap.put(account, amount); + } else { + accountMap.put(account, feesAmount); + } } else { - this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE, - loanProductId, paymentTypeId, loanId, transactionId, transactionDate, feesAmount, isReversal); + GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.FEES_RECEIVABLE.getValue(), paymentTypeId); + if (accountMap.containsKey(account)) { + BigDecimal amount = accountMap.get(account).add(feesAmount); + accountMap.put(account, amount); + } else { + accountMap.put(account, feesAmount); + } } } @@ -225,19 +249,41 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); if (isIncomeFromFee) { - this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode, - ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate, - penaltiesAmount, isReversal, loanTransactionDTO.getPenaltyPayments()); + GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), paymentTypeId); + if (accountMap.containsKey(account)) { + BigDecimal amount = accountMap.get(account).add(penaltiesAmount); + accountMap.put(account, amount); + } else { + accountMap.put(account, penaltiesAmount); + } } else { - this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE, - loanProductId, paymentTypeId, loanId, transactionId, transactionDate, penaltiesAmount, isReversal); + GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.PENALTIES_RECEIVABLE.getValue(), paymentTypeId); + if (accountMap.containsKey(account)) { + BigDecimal amount = accountMap.get(account).add(penaltiesAmount); + accountMap.put(account, amount); + } else { + accountMap.put(account, penaltiesAmount); + } } } - + if (overPaymentAmount != null && !(overPaymentAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(overPaymentAmount); - this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT, loanProductId, - paymentTypeId, loanId, transactionId, transactionDate, overPaymentAmount, isReversal); + GLAccount account = this.helper.getLinkedGLAccountForLoanProduct(loanProductId, + ACCRUAL_ACCOUNTS_FOR_LOAN.OVERPAYMENT.getValue(), paymentTypeId); + if (accountMap.containsKey(account)) { + BigDecimal amount = accountMap.get(account).add(overPaymentAmount); + accountMap.put(account, amount); + } else { + accountMap.put(account, overPaymentAmount); + } + } + + for (Entry entry : accountMap.entrySet()) { + this.helper.createCreditJournalEntryOrReversalForLoan(office, currencyCode, loanId, transactionId, transactionDate, + entry.getValue(), isReversal, entry.getKey()); } /** @@ -367,6 +413,7 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess paymentTypeId, loanId, transactionId, transactionDate, refundAmount, isReversal); } } + private void createJournalEntriesForRefundForActiveLoan(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO, Office office) { // TODO Auto-generated method stub // loan properties @@ -401,12 +448,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess if (feesAmount != null && !(feesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(feesAmount); - + List chargePaymentDTOs = new ArrayList<>(); - - for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) { - chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), - chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() )); + + for (ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getFeePayments()) { + chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), + chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)) + : chargePaymentDTO.getAmount())); } this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_FEES.getValue(), loanProductId, loanId, transactionId, transactionDate, feesAmount, @@ -416,12 +464,13 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess if (penaltiesAmount != null && !(penaltiesAmount.compareTo(BigDecimal.ZERO) == 0)) { totalDebitAmount = totalDebitAmount.add(penaltiesAmount); List chargePaymentDTOs = new ArrayList<>(); - - for(ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) { - chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), - chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)):chargePaymentDTO.getAmount() )); + + for (ChargePaymentDTO chargePaymentDTO : loanTransactionDTO.getPenaltyPayments()) { + chargePaymentDTOs.add(new ChargePaymentDTO(chargePaymentDTO.getChargeId(), chargePaymentDTO.getLoanChargeId(), + chargePaymentDTO.getAmount().floatValue() < 0 ? chargePaymentDTO.getAmount().multiply(new BigDecimal(-1)) + : chargePaymentDTO.getAmount())); } - + this.helper.createCreditJournalEntryOrReversalForLoanCharges(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.INCOME_FROM_PENALTIES.getValue(), loanProductId, loanId, transactionId, transactionDate, penaltiesAmount, !isReversal, chargePaymentDTOs); @@ -434,8 +483,8 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess } /*** create a single debit entry (or reversal) for the entire amount **/ - this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), loanProductId, - paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal); - + this.helper.createDebitJournalEntryOrReversalForLoan(office, currencyCode, CASH_ACCOUNTS_FOR_LOAN.FUND_SOURCE.getValue(), + loanProductId, paymentTypeId, loanId, transactionId, transactionDate, totalDebitAmount, !isReversal); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java index cec29fbd2..0580a4c4e 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobName.java @@ -39,8 +39,9 @@ public enum JobName { RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans"), // GENERATE_RD_SCEHDULE("Generate Mandatory Savings Schedule"), // GENERATE_LOANLOSS_PROVISIONING("Generate Loan Loss Provisioning"), // - POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), - UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"); + POST_DIVIDENTS_FOR_SHARES("Post Dividends For Shares"), // + UPDATE_SAVINGS_DORMANT_ACCOUNTS("Update Savings Dormant Accounts"), // + ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS("Add Accrual Transactions For Loans With Income Posted As Transactions"); private final String name; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/CalendarConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/CalendarConstants.java index a15a71b9d..b96eee544 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/CalendarConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/CalendarConstants.java @@ -31,7 +31,8 @@ public class CalendarConstants { "typeId"), REPEATING("repeating"), REMIND_BY_ID("remindById"), FIRST_REMINDER("firstReminder"), SECOND_REMINDER( "secondReminder"), LOCALE("locale"), DATE_FORMAT("dateFormat"), FREQUENCY("frequency"), INTERVAL("interval"), REPEATS_ON_DAY( "repeatsOnDay"), RESCHEDULE_BASED_ON_MEETING_DATES("reschedulebasedOnMeetingDates"), PRESENT_MEETING_DATE( - "presentMeetingDate"), NEW_MEETING_DATE("newMeetingDate"),MEETING_TIME("meetingtime"),Time_Format("timeFormat"), ; + "presentMeetingDate"), NEW_MEETING_DATE("newMeetingDate"),MEETING_TIME("meetingtime"),Time_Format("timeFormat"), REPEATS_ON_NTH_DAY_OF_MONTH("repeatsOnNthDayOfMonth"), + REPEATS_ON_LAST_WEEKDAY_OF_MONTH("repeatsOnLastWeekdayOfMonth"), REPEATS_ON_DAY_OF_MONTH("repeatsOnDayOfMonth"); private final String value; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/api/CalendarsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/api/CalendarsApiResource.java index f2ac60857..418c902ca 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/api/CalendarsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/api/CalendarsApiResource.java @@ -230,8 +230,9 @@ public class CalendarsApiResource { final List remindByOptions = this.dropdownReadPlatformService.retrieveCalendarRemindByOptions(); final List frequencyOptions = this.dropdownReadPlatformService.retrieveCalendarFrequencyTypeOptions(); final List repeatsOnDayOptions = this.dropdownReadPlatformService.retrieveCalendarWeekDaysTypeOptions(); + final List frequencyNthDayTypeOptions = this.dropdownReadPlatformService.retrieveCalendarFrequencyNthDayTypeOptions(); return CalendarData.withTemplateOptions(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions, frequencyOptions, - repeatsOnDayOptions); + repeatsOnDayOptions, frequencyNthDayTypeOptions); } private CommandWrapper getResourceDetails(final CalendarEntityType type, final Long entityId) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java index de899231f..2c6513a76 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarData.java @@ -28,6 +28,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarType; import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; +import org.apache.fineract.portfolio.common.domain.NthDayType; import org.joda.time.LocalDate; import org.joda.time.LocalTime; @@ -53,6 +54,7 @@ public class CalendarData { private final EnumOptionData frequency; private final Integer interval; private final EnumOptionData repeatsOnDay; + private final EnumOptionData repeatsOnNthDayOfMonth; private final EnumOptionData remindBy; private final Integer firstReminder; private final Integer secondReminder; @@ -67,20 +69,24 @@ public class CalendarData { private final String createdByUsername; private final Long lastUpdatedByUserId; private final String lastUpdatedByUsername; + private final Integer repeatsOnDayOfMonth; + // template related final List entityTypeOptions; final List calendarTypeOptions; final List remindByOptions; final List frequencyOptions; final List repeatsOnDayOptions; + final List frequencyNthDayTypeOptions; public static CalendarData instance(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType, final String title, final String description, final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration, final EnumOptionData type, final boolean repeating, final String recurrence, - final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay, final EnumOptionData remindBy, - final Integer firstReminder, final Integer secondReminder, final String humanReadable, final LocalDate createdDate, - final LocalDate lastUpdatedDate, final Long createdByUserId, final String createdByUsername, final Long lastUpdatedByUserId, - final String lastUpdatedByUsername, final LocalTime meetingTime) { + final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay, + final EnumOptionData repeatsOnNthDayOfMonth, final EnumOptionData remindBy, final Integer firstReminder, + final Integer secondReminder, final String humanReadable, final LocalDate createdDate, final LocalDate lastUpdatedDate, + final Long createdByUserId, final String createdByUsername, final Long lastUpdatedByUserId, final String lastUpdatedByUsername, + final LocalTime meetingTime, final Integer repeatsOnDayOfMonth) { final Collection recurringDates = null; final Collection nextTenRecurringDates = null; @@ -91,12 +97,14 @@ public class CalendarData { final List remindByOptions = null; final List frequencyOptions = null; final List repeatsOnDayOptions = null; + final List frequencyNthDayTypeOptions = null; return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration, - type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates, - nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId, - createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions, - frequencyOptions, repeatsOnDayOptions, meetingTime); + type, repeating, recurrence, frequency, interval, repeatsOnDay, repeatsOnNthDayOfMonth, remindBy, firstReminder, + secondReminder, recurringDates, nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, + lastUpdatedDate, createdByUserId, createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, repeatsOnDayOfMonth, + entityTypeOptions, calendarTypeOptions, remindByOptions, frequencyOptions, repeatsOnDayOptions, meetingTime, + frequencyNthDayTypeOptions); } public static CalendarData withRecurringDates(final CalendarData calendarData, final Collection recurringDates, @@ -104,24 +112,24 @@ public class CalendarData { return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, - calendarData.interval, calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, + calendarData.interval, calendarData.repeatsOnDay, calendarData.repeatsOnNthDayOfMonth, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, recurringDates, nextTenRecurringDates, calendarData.humanReadable, recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate, calendarData.createdByUserId, calendarData.createdByUsername, - calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername, calendarData.entityTypeOptions, + calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername, calendarData.repeatsOnDayOfMonth, calendarData.entityTypeOptions, calendarData.calendarTypeOptions, calendarData.remindByOptions, calendarData.frequencyOptions, - calendarData.repeatsOnDayOptions,calendarData.meetingTime); + calendarData.repeatsOnDayOptions, calendarData.meetingTime, calendarData.frequencyNthDayTypeOptions); } public static CalendarData withRecentEligibleMeetingDate(final CalendarData calendarData, final LocalDate recentEligibleMeetingDate) { return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, - calendarData.interval, calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, + calendarData.interval, calendarData.repeatsOnDay, calendarData.repeatsOnNthDayOfMonth, calendarData.remindBy, calendarData.firstReminder, calendarData.secondReminder, calendarData.recurringDates, calendarData.nextTenRecurringDates, calendarData.humanReadable, recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate, calendarData.createdByUserId, calendarData.createdByUsername, calendarData.lastUpdatedByUserId, calendarData.lastUpdatedByUsername, - calendarData.entityTypeOptions, calendarData.calendarTypeOptions, calendarData.remindByOptions, - calendarData.frequencyOptions, calendarData.repeatsOnDayOptions,calendarData.meetingTime); + calendarData.repeatsOnDayOfMonth, calendarData.entityTypeOptions, calendarData.calendarTypeOptions, calendarData.remindByOptions, + calendarData.frequencyOptions, calendarData.repeatsOnDayOptions,calendarData.meetingTime, calendarData.frequencyNthDayTypeOptions); } public static CalendarData sensibleDefaultsForNewCalendarCreation() { @@ -141,6 +149,7 @@ public class CalendarData { final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarFrequencyType.DAILY); final Integer interval = new Integer(1); final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.MO); + final EnumOptionData repeatsOnNthDayOfMonth = CalendarEnumerations.calendarFrequencyNthDayType(NthDayType.ONE); final EnumOptionData remindBy = CalendarEnumerations.calendarRemindBy(CalendarRemindBy.EMAIL); final Integer firstReminder = new Integer(0); final Integer secondReminder = new Integer(0); @@ -154,6 +163,7 @@ public class CalendarData { final List remindByOptions = null; final List frequencyOptions = null; final List repeatsOnDayOptions = null; + final List frequencyNthDayTypeOptions = null; final LocalDate createdDate = null; final LocalDate lastUpdatedDate = null; @@ -162,39 +172,44 @@ public class CalendarData { final Long lastUpdatedByUserId = null; final String lastUpdatedByUsername = null; final LocalTime meetingTime = null; + final Integer repeatsOnDayOfMonth = null; return new CalendarData(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, duration, - type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, recurringDates, - nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, lastUpdatedDate, createdByUserId, - createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions, - frequencyOptions, repeatsOnDayOptions, meetingTime); + type, repeating, recurrence, frequency, interval, repeatsOnDay, repeatsOnNthDayOfMonth, remindBy, firstReminder, + secondReminder, recurringDates, nextTenRecurringDates, humanReadable, recentEligibleMeetingDate, createdDate, + lastUpdatedDate, createdByUserId, createdByUsername, lastUpdatedByUserId, lastUpdatedByUsername, repeatsOnDayOfMonth, + entityTypeOptions, calendarTypeOptions, remindByOptions, frequencyOptions, repeatsOnDayOptions, meetingTime, + frequencyNthDayTypeOptions); } public static CalendarData withTemplateOptions(final CalendarData calendarData, final List entityTypeOptions, final List calendarTypeOptions, final List remindByOptions, - final List repeatsOptions, final List repeatsOnDayOptions) { + final List repeatsOptions, final List repeatsOnDayOptions, + final List frequencyNthDayTypeOptions) { return new CalendarData(calendarData.id, calendarData.calendarInstanceId, calendarData.entityId, calendarData.entityType, calendarData.title, calendarData.description, calendarData.location, calendarData.startDate, calendarData.endDate, calendarData.duration, calendarData.type, calendarData.repeating, calendarData.recurrence, calendarData.frequency, - calendarData.interval, calendarData.repeatsOnDay, calendarData.remindBy, calendarData.firstReminder, - calendarData.secondReminder, calendarData.recurringDates, calendarData.nextTenRecurringDates, calendarData.humanReadable, - calendarData.recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate, + calendarData.interval, calendarData.repeatsOnDay, calendarData.repeatsOnNthDayOfMonth, calendarData.remindBy, + calendarData.firstReminder, calendarData.secondReminder, calendarData.recurringDates, calendarData.nextTenRecurringDates, + calendarData.humanReadable, calendarData.recentEligibleMeetingDate, calendarData.createdDate, calendarData.lastUpdatedDate, calendarData.createdByUserId, calendarData.createdByUsername, calendarData.lastUpdatedByUserId, - calendarData.lastUpdatedByUsername, entityTypeOptions, calendarTypeOptions, remindByOptions, repeatsOptions, - repeatsOnDayOptions,calendarData.meetingTime); + calendarData.lastUpdatedByUsername, calendarData.repeatsOnDayOfMonth, entityTypeOptions, calendarTypeOptions, + remindByOptions, repeatsOptions, repeatsOnDayOptions, calendarData.meetingTime, frequencyNthDayTypeOptions); } private CalendarData(final Long id, final Long calendarInstanceId, final Long entityId, final EnumOptionData entityType, final String title, final String description, final String location, final LocalDate startDate, final LocalDate endDate, final Integer duration, final EnumOptionData type, final boolean repeating, final String recurrence, - final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay, final EnumOptionData remindBy, - final Integer firstReminder, final Integer secondReminder, final Collection recurringDates, - final Collection nextTenRecurringDates, final String humanReadable, final LocalDate recentEligibleMeetingDate, - final LocalDate createdDate, final LocalDate lastUpdatedDate, final Long createdByUserId, final String createdByUsername, - final Long lastUpdatedByUserId, final String lastUpdatedByUsername, final List entityTypeOptions, + final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay, + final EnumOptionData repeatsOnNthDayOfMonth, final EnumOptionData remindBy, final Integer firstReminder, + final Integer secondReminder, final Collection recurringDates, final Collection nextTenRecurringDates, + final String humanReadable, final LocalDate recentEligibleMeetingDate, final LocalDate createdDate, + final LocalDate lastUpdatedDate, final Long createdByUserId, final String createdByUsername, final Long lastUpdatedByUserId, + final String lastUpdatedByUsername, final Integer repeatsOnDayOfMonth, final List entityTypeOptions, final List calendarTypeOptions, final List remindByOptions, - final List repeatsOptions, final List repeatsOnDayOptions,final LocalTime meetingTime) { + final List repeatsOptions, final List repeatsOnDayOptions,final LocalTime meetingTime, + final List frequencyNthDayTypeOptions) { this.id = id; this.calendarInstanceId = calendarInstanceId; this.entityId = entityId; @@ -211,6 +226,7 @@ public class CalendarData { this.frequency = frequency; this.interval = interval; this.repeatsOnDay = repeatsOnDay; + this.repeatsOnNthDayOfMonth = repeatsOnNthDayOfMonth; this.remindBy = remindBy; this.firstReminder = firstReminder; this.secondReminder = secondReminder; @@ -224,12 +240,14 @@ public class CalendarData { this.createdByUsername = createdByUsername; this.lastUpdatedByUserId = lastUpdatedByUserId; this.lastUpdatedByUsername = lastUpdatedByUsername; + this.repeatsOnDayOfMonth = repeatsOnDayOfMonth; this.entityTypeOptions = entityTypeOptions; this.calendarTypeOptions = calendarTypeOptions; this.remindByOptions = remindByOptions; this.frequencyOptions = repeatsOptions; this.repeatsOnDayOptions = repeatsOnDayOptions; this.meetingTime = meetingTime; + this.frequencyNthDayTypeOptions = frequencyNthDayTypeOptions; } public Long getId() { @@ -350,4 +368,10 @@ public class CalendarData { public EnumOptionData frequencyType(){ return this.frequency; } + public EnumOptionData getRepeatsOnDay() { + return this.repeatsOnDay; + } + public EnumOptionData getRepeatsOnNthDayOfMonth() { + return this.repeatsOnNthDayOfMonth; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java index f44dfdd51..f2fa718c5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java @@ -48,6 +48,7 @@ import org.apache.fineract.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORT import org.apache.fineract.portfolio.calendar.exception.CalendarDateException; import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; +import org.apache.fineract.portfolio.common.domain.NthDayType; import org.apache.fineract.useradministration.domain.AppUser; import org.apache.poi.openxml4j.util.Nullable; import org.joda.time.LocalDate; @@ -152,8 +153,9 @@ public class Calendar extends AbstractAuditableCustom { } public static Calendar createRepeatingCalendar(final String title, final LocalDate startDate, final Integer typeId, - final CalendarFrequencyType frequencyType, final Integer interval, final Integer repeatsOnDay) { - final String recurrence = constructRecurrence(frequencyType, interval, repeatsOnDay); + final CalendarFrequencyType frequencyType, final Integer interval, final Integer repeatsOnDay, + final Integer repeatsOnNthDayOfMonth) { + final String recurrence = constructRecurrence(frequencyType, interval, repeatsOnDay, repeatsOnNthDayOfMonth); return createRepeatingCalendar(title, startDate, typeId, recurrence); } @@ -245,7 +247,7 @@ public class Calendar extends AbstractAuditableCustom { // TODO cover other recurrence also - this.recurrence = constructRecurrence(calendarFrequencyType, interval, repeatsOnDay); + this.recurrence = constructRecurrence(calendarFrequencyType, interval, repeatsOnDay, null); } @@ -422,7 +424,7 @@ public class Calendar extends AbstractAuditableCustom { @SuppressWarnings("null") public Map updateRepeatingCalendar(final LocalDate calendarStartDate, final CalendarFrequencyType frequencyType, - final Integer interval, final Integer repeatsOnDay) { + final Integer interval, final Integer repeatsOnDay, final Integer repeatsOnNthDay) { final Map actualChanges = new LinkedHashMap<>(9); if (calendarStartDate != null & this.startDate != null) { @@ -432,7 +434,7 @@ public class Calendar extends AbstractAuditableCustom { } } - final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, repeatsOnDay); + final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, repeatsOnDay, repeatsOnNthDay); if (!StringUtils.isBlank(this.recurrence) && !newRecurrence.equalsIgnoreCase(this.recurrence)) { actualChanges.put("recurrence", newRecurrence); this.recurrence = newRecurrence; @@ -568,13 +570,27 @@ public class Calendar extends AbstractAuditableCustom { if (frequencyType.isWeekly()) { repeatsOnDay = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue()); } + Integer repeatsOnNthDayOfMonth = null; + if (frequencyType.isMonthly()) { + repeatsOnNthDayOfMonth = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_NTH_DAY_OF_MONTH + .getValue()); + final NthDayType nthDay = NthDayType.fromInt(repeatsOnNthDayOfMonth); + repeatsOnDay = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_LAST_WEEKDAY_OF_MONTH + .getValue()); + if (nthDay.isOnDay()) { + repeatsOnNthDayOfMonth = command.integerValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY_OF_MONTH + .getValue()); + repeatsOnDay = null; + } + } - return constructRecurrence(frequencyType, interval, repeatsOnDay); + return constructRecurrence(frequencyType, interval, repeatsOnDay, repeatsOnNthDayOfMonth); } return ""; } - private static String constructRecurrence(final CalendarFrequencyType frequencyType, final Integer interval, final Integer repeatsOnDay) { + private static String constructRecurrence(final CalendarFrequencyType frequencyType, final Integer interval, + final Integer repeatsOnDay, final Integer repeatsOnNthDayOfMonth) { final StringBuilder recurrenceBuilder = new StringBuilder(200); recurrenceBuilder.append("FREQ="); @@ -584,10 +600,31 @@ public class Calendar extends AbstractAuditableCustom { recurrenceBuilder.append(interval); } if (frequencyType.isWeekly()) { - final CalendarWeekDaysType weekDays = CalendarWeekDaysType.fromInt(repeatsOnDay); - if (!weekDays.isInvalid()) { - recurrenceBuilder.append(";BYDAY="); - recurrenceBuilder.append(weekDays.toString().toUpperCase()); + if (repeatsOnDay != null) { + final CalendarWeekDaysType weekDays = CalendarWeekDaysType.fromInt(repeatsOnDay); + if (!weekDays.isInvalid()) { + recurrenceBuilder.append(";BYDAY="); + recurrenceBuilder.append(weekDays.toString().toUpperCase()); + } + } + } + if (frequencyType.isMonthly()) { + if (repeatsOnNthDayOfMonth != null && (repeatsOnDay == null || repeatsOnDay == CalendarWeekDaysType.INVALID.getValue())) { + if (repeatsOnNthDayOfMonth >= -1 && repeatsOnNthDayOfMonth <= 28) { + recurrenceBuilder.append(";BYMONTHDAY="); + recurrenceBuilder.append(repeatsOnNthDayOfMonth); + } + } else if (repeatsOnNthDayOfMonth != null && repeatsOnDay != null && repeatsOnDay != CalendarWeekDaysType.INVALID.getValue()) { + final NthDayType nthDay = NthDayType.fromInt(repeatsOnNthDayOfMonth); + if (!nthDay.isInvalid()) { + recurrenceBuilder.append(";BYSETPOS="); + recurrenceBuilder.append(nthDay.getValue()); + } + final CalendarWeekDaysType weekday = CalendarWeekDaysType.fromInt(repeatsOnDay); + if (!weekday.isInvalid()) { + recurrenceBuilder.append(";BYDAY="); + recurrenceBuilder.append(weekday.toString().toUpperCase()); + } } } return recurrenceBuilder.toString(); @@ -611,18 +648,22 @@ public class Calendar extends AbstractAuditableCustom { final CalendarFrequencyType frequencyType = CalendarUtils.getFrequency(this.recurrence); final Integer interval = new Integer(CalendarUtils.getInterval(this.recurrence)); - final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, startDate.getDayOfWeek()); + final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, startDate.getDayOfWeek(), null); this.recurrence = newRecurrence; this.startDate = startDate.toDate(); this.endDate = endDate.toDate(); } - - public Set getCalendarHistory(){ - return this.calendarHistory; + + public Set getCalendarHistory() { + return this.calendarHistory; } - - public void updateCalendarHistory(final Set calendarHistory){ - this.calendarHistory = calendarHistory; + + public void updateCalendarHistory(final Set calendarHistory) { + this.calendarHistory = calendarHistory; + } + + public void setRecurrence(String recurrence) { + this.recurrence = recurrence; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstance.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstance.java index 6c40b04e5..6ae6c60d5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstance.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/CalendarInstance.java @@ -31,7 +31,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable; @Table(name = "m_calendar_instance") public class CalendarInstance extends AbstractPersistable { - @ManyToOne(cascade = CascadeType.ALL) + @ManyToOne(cascade = CascadeType.PERSIST) @JoinColumn(name = "calendar_id", nullable = false) private Calendar calendar; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java index c956afa62..fcdd2e23b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/serialization/CalendarCommandFromApiJsonDeserializer.java @@ -37,6 +37,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy; import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.springframework.beans.factory.annotation.Autowired; @@ -173,6 +174,10 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element); baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue()).value(repeatsOnDay) .notBlank().inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue()); + } else if (CalendarFrequencyType.fromInt(frequency).isMonthly()) { + CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, + CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_NTH_DAY_OF_MONTH.getValue(), + CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_LAST_WEEKDAY_OF_MONTH.getValue(), element, this.fromApiJsonHelper); } } } @@ -305,11 +310,17 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD .integerGreaterThanZero(); } + if (CalendarFrequencyType.fromInt(frequency).isWeekly()) { if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element)) { final Integer repeatsOnDay = this.fromApiJsonHelper.extractIntegerSansLocaleNamed( CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue(), element); baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_DAY.getValue()).value(repeatsOnDay) .notBlank().inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue()); + } + } else if (CalendarFrequencyType.fromInt(frequency).isMonthly()) { + CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, + CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_NTH_DAY_OF_MONTH.getValue(), + CALENDAR_SUPPORTED_PARAMETERS.REPEATS_ON_LAST_WEEKDAY_OF_MONTH.getValue(), element, this.fromApiJsonHelper); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformService.java index 1c95c7722..c670643db 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformService.java @@ -33,5 +33,6 @@ public interface CalendarDropdownReadPlatformService { List retrieveCalendarFrequencyTypeOptions(); List retrieveCalendarWeekDaysTypeOptions(); + List retrieveCalendarFrequencyNthDayTypeOptions(); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformServiceImpl.java index 713a38cef..78811800c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarDropdownReadPlatformServiceImpl.java @@ -55,4 +55,8 @@ public class CalendarDropdownReadPlatformServiceImpl implements CalendarDropdown public List retrieveCalendarWeekDaysTypeOptions() { return CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.values()); } + @Override + public List retrieveCalendarFrequencyNthDayTypeOptions() { + return CalendarEnumerations.calendarFrequencyNthDayType(); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarEnumerations.java index e96dba6eb..15c74ecb7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarEnumerations.java @@ -19,6 +19,7 @@ package org.apache.fineract.portfolio.calendar.service; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.apache.fineract.infrastructure.core.data.EnumOptionData; @@ -27,6 +28,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; import org.apache.fineract.portfolio.calendar.domain.CalendarRemindBy; import org.apache.fineract.portfolio.calendar.domain.CalendarType; import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; +import org.apache.fineract.portfolio.common.domain.NthDayType; public class CalendarEnumerations { @@ -86,8 +88,11 @@ public class CalendarEnumerations { } public static EnumOptionData calendarFrequencyType(final CalendarFrequencyType calendarFrequencyType) { - final EnumOptionData optionData = new EnumOptionData(calendarFrequencyType.getValue().longValue(), calendarFrequencyType.getCode(), + EnumOptionData optionData = null; + if (!calendarFrequencyType.isInvalid()) { + optionData = new EnumOptionData(calendarFrequencyType.getValue().longValue(), calendarFrequencyType.getCode(), calendarFrequencyType.toString()); + } return optionData; } @@ -106,8 +111,11 @@ public class CalendarEnumerations { } public static EnumOptionData calendarWeekDaysType(final CalendarWeekDaysType calendarWeekDaysType) { - final EnumOptionData optionData = new EnumOptionData(calendarWeekDaysType.getValue().longValue(), calendarWeekDaysType.getCode(), + EnumOptionData optionData = null; + if (!calendarWeekDaysType.isInvalid()) { + optionData = new EnumOptionData(calendarWeekDaysType.getValue().longValue(), calendarWeekDaysType.getCode(), calendarWeekDaysType.toString()); + } return optionData; } @@ -120,4 +128,19 @@ public class CalendarEnumerations { } return optionDatas; } + public static EnumOptionData calendarFrequencyNthDayType(final int id) { + return calendarFrequencyNthDayType(NthDayType.fromInt(id)); + } + public static EnumOptionData calendarFrequencyNthDayType(final NthDayType calendarFrequencyNthDayType) { + final EnumOptionData optionData = new EnumOptionData(calendarFrequencyNthDayType.getValue().longValue(), calendarFrequencyNthDayType.getCode(), + calendarFrequencyNthDayType.toString()); + return optionData; + } + public static List calendarFrequencyNthDayType() { + final List optionDatas = Arrays.asList(calendarFrequencyNthDayType(NthDayType.ONE), + calendarFrequencyNthDayType(NthDayType.TWO), calendarFrequencyNthDayType(NthDayType.THREE), + calendarFrequencyNthDayType(NthDayType.FOUR), calendarFrequencyNthDayType(NthDayType.LAST), + calendarFrequencyNthDayType(NthDayType.ONDAY)); + return optionDatas; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java index 3b5c37c04..c9e706875 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarReadPlatformServiceImpl.java @@ -89,6 +89,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence)); final Integer interval = new Integer(CalendarUtils.getInterval(recurrence)); final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence)); + final EnumOptionData repeatsOnNthDayOfMonth = CalendarEnumerations.calendarFrequencyNthDayType(CalendarUtils.getRepeatsOnNthDayOfMonth(recurrence)); final Integer remindById = rs.getInt("remindById"); EnumOptionData remindBy = null; if (remindById != null && remindById != 0) { @@ -100,7 +101,7 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ if (startDate != null && recurrence != null) { humanReadable = CalendarUtils.getRRuleReadable(startDate, recurrence); } - + Integer monthOnDay = CalendarUtils.getMonthOnDay(recurrence); final LocalDate createdDate = JdbcSupport.getLocalDate(rs, "createdDate"); final LocalDate lastUpdatedDate = JdbcSupport.getLocalDate(rs, "updatedDate"); final Long createdByUserId = rs.getLong("creatingUserId"); @@ -110,9 +111,9 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ final LocalTime meetingTime = JdbcSupport.getLocalTime(rs,"meetingTime"); return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, - duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, + duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, repeatsOnNthDayOfMonth, remindBy, firstReminder, secondReminder, humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId, - lastUpdatedByUserName,meetingTime); + lastUpdatedByUserName,meetingTime, monthOnDay); } } @@ -479,6 +480,8 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ final EnumOptionData frequency = CalendarEnumerations.calendarFrequencyType(CalendarUtils.getFrequency(recurrence)); final Integer interval = new Integer(CalendarUtils.getInterval(recurrence)); final EnumOptionData repeatsOnDay = CalendarEnumerations.calendarWeekDaysType(CalendarUtils.getRepeatsOnDay(recurrence)); + final EnumOptionData repeatsOnNthDayOfMonth = CalendarEnumerations.calendarFrequencyNthDayType(CalendarUtils + .getRepeatsOnNthDayOfMonth(recurrence)); final Integer remindById = rs.getInt("remindById"); EnumOptionData remindBy = null; if (remindById != null && remindById != 0) { @@ -498,11 +501,12 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ final Long lastUpdatedByUserId = null; final String lastUpdatedByUserName = null; final LocalTime meetingTime = null; + Integer monthOnDay = CalendarUtils.getMonthOnDay(recurrence); return CalendarData.instance(id, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, - duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, remindBy, firstReminder, secondReminder, - humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, lastUpdatedByUserId, - lastUpdatedByUserName, meetingTime); + duration, type, repeating, recurrence, frequency, interval, repeatsOnDay, repeatsOnNthDayOfMonth, remindBy, + firstReminder, secondReminder, humanReadable, createdDate, lastUpdatedDate, createdByUserId, createdByUserName, + lastUpdatedByUserId, lastUpdatedByUserName, meetingTime, monthOnDay); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java index a52560686..84c53b115 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java @@ -30,6 +30,7 @@ import java.util.StringTokenizer; import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.DateList; import net.fortuna.ical4j.model.DateTime; +import net.fortuna.ical4j.model.NumberList; import net.fortuna.ical4j.model.Recur; import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.WeekDay; @@ -37,18 +38,23 @@ import net.fortuna.ical4j.model.WeekDayList; import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.model.property.RRule; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; +import org.apache.fineract.portfolio.common.domain.NthDayType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import com.google.gson.JsonElement; + public class CalendarUtils { static { @@ -260,10 +266,48 @@ public class CalendarUtils { } } else if (recur.getFrequency().equals(Recur.MONTHLY)) { - if (recur.getInterval() == 1) { + NumberList nthDays = recur.getSetPosList(); + Integer nthDay = null; + if (!nthDays.isEmpty()) + nthDay = (Integer) nthDays.get(0); + NumberList monthDays = recur.getMonthDayList(); + Integer monthDay = null; + if (!monthDays.isEmpty()) + monthDay = (Integer) monthDays.get(0); + WeekDayList weekdays = recur.getDayList(); + WeekDay weekDay = null; + if (!weekdays.isEmpty()) + weekDay = (WeekDay) weekdays.get(0); + if (nthDay != null && weekDay != null) { + NthDayType nthDayType = NthDayType.fromInt(nthDay); + NthDayNameEnum nthDayName = NthDayNameEnum.from(nthDayType.toString()); + DayNameEnum weekdayType = DayNameEnum.from(weekDay.getDay()); + if (recur.getInterval() == 1 || recur.getInterval() == -1) { + humanReadable = "Monthly on " + nthDayName.getCode().toLowerCase() + " " + weekdayType.getCode().toLowerCase(); + } else { + humanReadable = "Every " + recur.getInterval() + " months on " + nthDayName.getCode().toLowerCase() + " " + + weekdayType.getCode().toLowerCase(); + } + } else if (monthDay != null) { + if (monthDay == -1) { + if (recur.getInterval() == 1 || recur.getInterval() == -1) { + humanReadable = "Monthly on last day"; + } else { + humanReadable = "Every " + recur.getInterval() + " months on last day"; + } + } else { + if (recur.getInterval() == 1 || recur.getInterval() == -1) { + humanReadable = "Monthly on day " + monthDay; + } else { + humanReadable = "Every " + recur.getInterval() + " months on day " + monthDay; + } + } + } else { + if (recur.getInterval() == 1 || recur.getInterval() == -1) { humanReadable = "Monthly on day " + startDate.getDayOfMonth(); } else { humanReadable = "Every " + recur.getInterval() + " months on day " + startDate.getDayOfMonth(); + } } } else if (recur.getFrequency().equals(Recur.YEARLY)) { if (recur.getInterval() == 1) { @@ -347,6 +391,28 @@ public class CalendarUtils { return DayNameEnum.MO;// Default it to Monday } } + public static enum NthDayNameEnum { + ONE(1, "First"), TWO(2, "Second"), THREE(3, "Third"), FOUR(4, "Fourth"), FIVE(5, "Fifth"), LAST(-1, "Last"), INVALID(0, "Invalid"); + private final String code; + private final Integer value; + + private NthDayNameEnum(final Integer value, final String code) { + this.value = value; + this.code = code; + } + public String getCode() { + return this.code; + } + public int getValue() { + return this.value; + } + public static NthDayNameEnum from(final String name) { + for (final NthDayNameEnum nthDayName : NthDayNameEnum.values()) { + if (nthDayName.toString().equals(name)) { return nthDayName; } + } + return NthDayNameEnum.INVALID; + } + } public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule) { final Recur recur = CalendarUtils.getICalRecur(recurringRule); @@ -399,6 +465,18 @@ public class CalendarUtils { WeekDay weekDay = (WeekDay) weekDays.get(0); return CalendarWeekDaysType.fromString(weekDay.getDay()); } + public static NthDayType getRepeatsOnNthDayOfMonth(final String recurringRule) { + final Recur recur = CalendarUtils.getICalRecur(recurringRule); + NumberList monthDays = null; + if(recur.getDayList().isEmpty()) + monthDays = recur.getMonthDayList(); + else + monthDays = recur.getSetPosList(); + if (monthDays.isEmpty()) return NthDayType.INVALID; + if (!recur.getMonthDayList().isEmpty() && recur.getSetPosList().isEmpty()) return NthDayType.ONDAY; + Integer monthDay = (Integer) monthDays.get(0); + return NthDayType.fromInt(monthDay); + } public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate, final Integer loanRepaymentInterval, final String frequency, boolean isSkipRepaymentOnFirstDayOfMonth, @@ -589,4 +667,37 @@ public class CalendarUtils { return scheduleDate; } + public static void validateNthDayOfMonthFrequency(DataValidatorBuilder baseDataValidator, final String repeatsOnNthDayOfMonthParamName, + final String repeatsOnDayParamName, final JsonElement element, final FromJsonHelper fromApiJsonHelper) { + final Integer repeatsOnNthDayOfMonth = fromApiJsonHelper.extractIntegerSansLocaleNamed(repeatsOnNthDayOfMonthParamName, element); + baseDataValidator.reset().parameter(repeatsOnNthDayOfMonthParamName).value(repeatsOnNthDayOfMonth).ignoreIfNull() + .isOneOfTheseValues(NthDayType.ONE.getValue(), NthDayType.TWO.getValue(), NthDayType.THREE.getValue(), + NthDayType.FOUR.getValue(), NthDayType.LAST.getValue(), NthDayType.ONDAY.getValue()); + final Integer repeatsOnDay = fromApiJsonHelper.extractIntegerSansLocaleNamed(repeatsOnDayParamName, element); + baseDataValidator.reset().parameter(repeatsOnDayParamName).value(repeatsOnDay).ignoreIfNull() + .inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue()); + NthDayType nthDayType = null; + if (repeatsOnNthDayOfMonth != null) { + nthDayType = NthDayType.fromInt(repeatsOnNthDayOfMonth); + } + if (nthDayType != null && nthDayType != NthDayType.INVALID) { + if (nthDayType == NthDayType.ONE || nthDayType == NthDayType.TWO || nthDayType == NthDayType.THREE + || nthDayType == NthDayType.FOUR) { + baseDataValidator.reset().parameter(repeatsOnDayParamName).value(repeatsOnDay).cantBeBlankWhenParameterProvidedIs( + repeatsOnNthDayOfMonthParamName, NthDayNameEnum.from(nthDayType.toString()).getCode().toLowerCase()); + } + } + } + public static Integer getMonthOnDay(String recurringRule) { + final Recur recur = CalendarUtils.getICalRecur(recurringRule); + NumberList monthDayList = null; + Integer monthOnDay = null; + if (getMeetingPeriodFrequencyType(recur).isMonthly()) { + monthDayList = recur.getMonthDayList(); + if (!monthDayList.isEmpty()) { + monthOnDay = (Integer) monthDayList.get(0); + } + } + return monthOnDay; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java index c987bd7ce..be01e2b60 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java @@ -33,7 +33,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; @@ -291,8 +290,10 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr if (reschedulebasedOnMeetingDates == null){ presentMeetingDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()); } - final Date endDate = presentMeetingDate.minusDays(1).toDate(); - calendarHistory.updateEndDate(endDate); + if (null != newMeetingDate) { + final Date endDate = presentMeetingDate.minusDays(1).toDate(); + calendarHistory.updateEndDate(endDate); + } this.calendarHistoryRepository.save(calendarHistory); Set history = calendarForUpdate.getCalendarHistory(); history.add(calendarHistory); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java index d32b2184e..7abb4454c 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java @@ -25,6 +25,8 @@ public enum NthDayType { THREE(3,"nthDayType.three"), FOUR(4,"nthDayType.four"), FIVE(5,"nthDayType.five"), + LAST(-1,"nthDayType.last"), + ONDAY(-2,"nthDayType.onday"), INVALID(0,"nthDayType.invalid"); private final Integer value; @@ -62,6 +64,12 @@ public enum NthDayType { case 5: repaymentFrequencyNthDayType = NthDayType.FIVE; break; + case -1: + repaymentFrequencyNthDayType = NthDayType.LAST; + break; + case -2: + repaymentFrequencyNthDayType = NthDayType.ONDAY; + break; default: break; } @@ -69,4 +77,13 @@ public enum NthDayType { return repaymentFrequencyNthDayType; } + public boolean isInvalid() { + return this.value.equals(NthDayType.INVALID.value); + } + public boolean isLastDay() { + return this.value.equals(NthDayType.LAST.value); + } + public boolean isOnDay() { + return this.value.equals(NthDayType.ONDAY.value); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java index c44b8e463..fc503b770 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java @@ -52,6 +52,7 @@ import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations; import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.fineract.portfolio.client.domain.ClientEnumerations; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; @@ -271,10 +272,12 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate"); final String recurrence = rs.getString("recurrence"); final LocalTime meetingTime = JdbcSupport.getLocalTime(rs,"meetingTime"); + Integer monthOnDay = CalendarUtils.getMonthOnDay(recurrence); CalendarData calendarData = CalendarData.instance(calendarId, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, null, null, false, recurrence, null, null, null, null, null, null, null, null, null, - null, null, null, null,meetingTime); + null, null, null, null, null, meetingTime, monthOnDay); + return CenterData.instance(id, accountNo, name, externalId, status, activationDate, officeId, null, staffId, staffName, hierarchy, null, calendarData); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java index 61ea80305..41d6c0b75 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java @@ -40,12 +40,49 @@ public interface LoanApiConstants { public static final String rejectedOnDateParameterName = "rejectedOnDate"; public static final String withdrawnOnDateParameterName = "withdrawnOnDate"; - // Interest recalculation related - public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled"; + public static final String transactionProcessingStrategyIdParameterName = "transactionProcessingStrategyId"; + public static final String loanPurposeIdParameterName = "loanPurposeId"; + public static final String loanOfficerIdParameterName = "loanOfficerId"; + public static final String fundIdParameterName = "fundId"; + public static final String externalIdParameterName = "externalId"; + public static final String accountNoParameterName = "accountNo"; + public static final String productIdParameterName = "productId"; + public static final String calendarIdParameterName = "calendarId"; + public static final String loanTypeParameterName = "loanType"; + public static final String groupIdParameterName = "groupId"; + public static final String clientIdParameterName = "clientId"; + public static final String idParameterName = "id"; + public static final String graceOnInterestChargedParameterName = "graceOnInterestCharged"; + public static final String graceOnInterestPaymentParameterName = "graceOnInterestPayment"; + public static final String graceOnPrincipalPaymentParameterName = "graceOnPrincipalPayment"; + public static final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate"; + public static final String interestRateFrequencyTypeParameterName = "interestRateFrequencyType"; + public static final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType"; + public static final String interestTypeParameterName = "interestType"; + public static final String amortizationTypeParameterName = "amortizationType"; + public static final String repaymentFrequencyTypeParameterName = "repaymentFrequencyType"; + public static final String loanTermFrequencyTypeParameterName = "loanTermFrequencyType"; + public static final String loanTermFrequencyParameterName = "loanTermFrequency"; + public static final String numberOfRepaymentsParameterName = "numberOfRepayments"; + public static final String repaymentEveryParameterName = "repaymentEvery"; + public static final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; + public static final String inArrearsToleranceParameterName = "inArrearsTolerance"; + public static final String interestChargedFromDateParameterName = "interestChargedFromDate"; + public static final String submittedOnDateParameterName = "submittedOnDate"; + public static final String submittedOnNoteParameterName = "interestChargedFromDate"; + public static final String collateralParameterName = "collateral"; + public static final String syncDisbursementWithMeetingParameterName = "syncDisbursementWithMeeting"; + public static final String linkAccountIdParameterName = "linkAccountId"; + public static final String createStandingInstructionAtDisbursementParameterName = "createStandingInstructionAtDisbursement"; public static final String daysInYearTypeParameterName = "daysInYearType"; public static final String daysInMonthTypeParameterName = "daysInMonthType"; + + // Interest recalculation related + public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled"; public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod"; public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod"; + public static final String repaymentFrequencyNthDayTypeParameterName = "repaymentFrequencyNthDayType"; + public static final String repaymentFrequencyDayOfWeekTypeParameterName = "repaymentFrequencyDayOfWeekType"; // Floating interest rate related public static final String interestRateDifferentialParameterName = "interestRateDifferential"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 929a5aec6..5e71e0337 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -372,6 +372,17 @@ public class LoansApiResource { loanBasicDetails = LoanAccountData.withInterestRecalculationCalendarData(loanBasicDetails, calendarData, compoundingCalendarData); } + if (loanBasicDetails.isMonthlyRepaymentFrequencyType()) { + Collection loanCalendarDatas = this.calendarReadPlatformService + .retrieveCalendarsByEntity(loanId, + CalendarEntityType.LOANS.getValue(), null); + CalendarData calendarData = null; + if (!CollectionUtils.isEmpty(loanCalendarDatas)) { + calendarData = loanCalendarDatas.iterator().next(); + } + if(calendarData != null) + loanBasicDetails = LoanAccountData.withLoanCalendarData(loanBasicDetails, calendarData); + } Collection interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanId); @@ -484,6 +495,8 @@ public class LoansApiResource { LoanProductData product = null; Collection loanTermFrequencyTypeOptions = null; Collection repaymentFrequencyTypeOptions = null; + Collection repaymentFrequencyNthDayTypeOptions = null; + Collection repaymentFrequencyDayOfWeekTypeOptions = null; Collection repaymentStrategyOptions = null; Collection interestRateFrequencyTypeOptions = null; Collection amortizationTypeOptions = null; @@ -506,6 +519,8 @@ public class LoansApiResource { loanBasicDetails.setProduct(product); loanTermFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions(); repaymentFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions(); + repaymentFrequencyNthDayTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyOptionsForNthDayOfMonth(); + repaymentFrequencyDayOfWeekTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyOptionsForDaysOfWeek(); interestRateFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); @@ -559,9 +574,10 @@ public class LoansApiResource { final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments, charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions, - null, null, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, - interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, - loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations, + repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions, + interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, + fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, loanCollateralOptions, + calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations, overdueCharges, paidInAdvanceTemplate, interestRatesPeriods); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index cc7fc8729..5975ec366 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -34,6 +34,7 @@ import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.collateral.data.CollateralData; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData; import org.apache.fineract.portfolio.fund.data.FundData; import org.apache.fineract.portfolio.group.data.GroupGeneralData; @@ -1143,6 +1144,33 @@ public class LoanAccountData { acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap); } + public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) { + return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, + acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, + acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, + acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, + acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, + acc.repaymentFrequencyType, calendarData.getRepeatsOnNthDayOfMonth(), calendarData.getRepeatsOnDay(), + acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, + acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, + acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType, + acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, + acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, acc.graceOnInterestCharged, + acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, + acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, + acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, + acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions, + acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions, + acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions, + acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, + acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, + acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, + acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, + acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, + acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, + acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap); + } + public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, @@ -1518,4 +1546,8 @@ public class LoanAccountData { return BigDecimal.ZERO; } + public boolean isMonthlyRepaymentFrequencyType() { + return (this.repaymentFrequencyType.getId().intValue() == PeriodFrequencyType.MONTHS.getValue()); + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java index 4b432e633..3cc45410e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java @@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.data; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.calendar.data.CalendarData; -import org.joda.time.LocalDate; public class LoanInterestRecalculationData { @@ -32,19 +31,29 @@ public class LoanInterestRecalculationData { private final CalendarData calendarData; private final EnumOptionData recalculationRestFrequencyType; private final Integer recalculationRestFrequencyInterval; - private final LocalDate recalculationRestFrequencyDate; + /* private final LocalDate recalculationRestFrequencyDate; */ + private final EnumOptionData recalculationRestFrequencyNthDay; + private final EnumOptionData recalculationRestFrequencyWeekday; + private final Integer recalculationRestFrequencyOnDay; private final EnumOptionData recalculationCompoundingFrequencyType; private final Integer recalculationCompoundingFrequencyInterval; - private final LocalDate recalculationCompoundingFrequencyDate; + /* private final LocalDate recalculationCompoundingFrequencyDate; */ + private final EnumOptionData recalculationCompoundingFrequencyNthDay; + private final EnumOptionData recalculationCompoundingFrequencyWeekday; + private final Integer recalculationCompoundingFrequencyOnDay; + private final Boolean isCompoundingToBePostedAsTransaction; @SuppressWarnings("unused") private final CalendarData compoundingCalendarData; + private final Boolean allowCompoundingOnEod; 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 CalendarData compoundingCalendarData, + final EnumOptionData recalculationRestFrequencyNthDay, final EnumOptionData recalculationRestFrequencyWeekday, + final Integer recalculationRestFrequencyOnDay, final CalendarData compoundingCalendarData, final EnumOptionData recalculationCompoundingFrequencyType, final Integer recalculationCompoundingFrequencyInterval, - final LocalDate recalculationCompoundingFrequencyDate) { + final EnumOptionData recalculationCompoundingFrequencyNthDay, final EnumOptionData recalculationCompoundingFrequencyWeekday, + final Integer recalculationCompoundingFrequencyOnDay, final Boolean isCompoundingToBePostedAsTransaction, final Boolean allowCompoundingOnEod) { this.id = id; this.loanId = loanId; this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; @@ -52,11 +61,17 @@ public class LoanInterestRecalculationData { this.calendarData = calendarData; this.recalculationRestFrequencyType = recalculationRestFrequencyType; this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval; - this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; + this.recalculationRestFrequencyNthDay = recalculationRestFrequencyNthDay; + this.recalculationRestFrequencyWeekday = recalculationRestFrequencyWeekday; + this.recalculationRestFrequencyOnDay = recalculationRestFrequencyOnDay; this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType; this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval; - this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate; + this.recalculationCompoundingFrequencyNthDay = recalculationCompoundingFrequencyNthDay; + this.recalculationCompoundingFrequencyWeekday = recalculationCompoundingFrequencyWeekday; + this.recalculationCompoundingFrequencyOnDay = recalculationCompoundingFrequencyOnDay; this.compoundingCalendarData = compoundingCalendarData; + this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; + this.allowCompoundingOnEod = allowCompoundingOnEod; } public static LoanInterestRecalculationData withCalendarData(final LoanInterestRecalculationData recalculationData, @@ -64,9 +79,12 @@ public class LoanInterestRecalculationData { return new LoanInterestRecalculationData(recalculationData.id, recalculationData.loanId, recalculationData.interestRecalculationCompoundingType, recalculationData.rescheduleStrategyType, calendarData, recalculationData.recalculationRestFrequencyType, recalculationData.recalculationRestFrequencyInterval, - recalculationData.recalculationRestFrequencyDate, compoundingCalendarData, + recalculationData.recalculationRestFrequencyNthDay, recalculationData.recalculationRestFrequencyWeekday, + recalculationData.recalculationRestFrequencyOnDay, compoundingCalendarData, recalculationData.recalculationCompoundingFrequencyType, recalculationData.recalculationCompoundingFrequencyInterval, - recalculationData.recalculationCompoundingFrequencyDate); + recalculationData.recalculationCompoundingFrequencyNthDay, recalculationData.recalculationCompoundingFrequencyWeekday, + recalculationData.recalculationCompoundingFrequencyOnDay, recalculationData.isCompoundingToBePostedAsTransaction, + recalculationData.allowCompoundingOnEod); } public Long getId() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 13fd9ce22..822b57534 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -24,6 +24,7 @@ import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -76,6 +77,7 @@ import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarHistory; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; +import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; @@ -115,7 +117,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplica import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; -import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; @@ -192,12 +193,6 @@ public class Loan extends AbstractPersistable { @Embedded private LoanProductRelatedDetail loanRepaymentScheduleDetail; - @Column(name = "repayment_frequency_nth_day_enum", nullable = true) - private Integer repaymentFrequencyNthDayType; - - @Column(name = "repayment_frequency_day_of_week_enum", nullable = true) - private Integer repaymentFrequencyDayOfWeekType; - @Column(name = "term_frequency", nullable = false) private Integer termFrequency; @@ -858,7 +853,7 @@ public class Loan extends AbstractPersistable { if (loanCharge.isOverdueInstallmentCharge()) { return loanCharge.getAmountPercentageAppliedTo(); } switch (loanCharge.getChargeCalculation()) { case PERCENT_OF_AMOUNT: - amount = getDerivedAmountForCharge(loanCharge); + amount = getDerivedAmountForCharge(loanCharge); break; case PERCENT_OF_AMOUNT_AND_INTEREST: final BigDecimal totalInterestCharged = getTotalInterest(); @@ -1135,7 +1130,8 @@ public class Loan extends AbstractPersistable { scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), - scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent()); + scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(), + scheduledLoanInstallment.getLoanCompoundingDetails()); addRepaymentScheduleInstallment(installment); } } @@ -1194,6 +1190,13 @@ public class Loan extends AbstractPersistable { } installment.updateAccrualPortion(interest, fee, penality); } + LoanRepaymentScheduleInstallment lastInstallment = this.repaymentScheduleInstallments + .get(this.repaymentScheduleInstallments.size() - 1); + for (LoanTransaction loanTransaction : accruals) { + if (loanTransaction.getTransactionDate().isAfter(lastInstallment.getDueDate()) && !loanTransaction.isReversed()) { + loanTransaction.reverse(); + } + } } private void updateAccrualsForNonPeriodicAccruals(final Collection accruals, final AppUser currentUser) { @@ -1265,12 +1268,6 @@ public class Loan extends AbstractPersistable { final String dateFormatAsInput = command.dateFormat(); final String localeAsInput = command.locale(); - final LocalDate recalculationRestFrequencyDate = command - .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); - final LocalDate recalculationCompoundingFrequencyDate = command - .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName); - updateLoanInterestRecalculationSettings(recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate, command, - actualChanges); final String accountNoParamName = "accountNo"; if (command.isChangeInStringParameterNamed(accountNoParamName, this.accountNumber)) { @@ -1556,37 +1553,6 @@ public class Loan extends AbstractPersistable { /** * Update interest recalculation settings if product configuration changes */ - 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, this.loanProduct - .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct - .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); - this.loanInterestRecalculationDetails.updateLoan(this); - } else { - - this.loanInterestRecalculationDetails.update(command, actualChanges); - } - } else { - this.loanInterestRecalculationDetails = null; - } - } private void updateOverdueScheduleInstallment(final LoanCharge loanCharge) { if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isActive()) { @@ -1911,8 +1877,7 @@ 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 LocalDate recalculationCompoundingFrequencyDate) { + final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { updateLoanSchedule(loanSchedule, currentUser); @@ -1933,14 +1898,6 @@ public class Loan extends AbstractPersistable { this.expectedFirstRepaymentOnDate = loanApplicationTerms.getRepaymentStartFromDate(); this.interestChargedFromDate = loanApplicationTerms.getInterestChargedFromDate(); - if (loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.MONTHS) { - this.repaymentFrequencyNthDayType = loanApplicationTerms.getNthDay(); - this.repaymentFrequencyDayOfWeekType = loanApplicationTerms.getWeekDayType().getValue(); - } else { - this.repaymentFrequencyNthDayType = NthDayType.INVALID.getValue(); - this.repaymentFrequencyDayOfWeekType = DayOfWeekType.INVALID.getValue(); - } - updateLoanScheduleDependentDerivedFields(); if (submittedOn.isAfter(DateUtils.getLocalDateOfTenant())) { @@ -1984,22 +1941,9 @@ public class Loan extends AbstractPersistable { * enabled */ if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { - Date restFrequencyDate = null; - 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, this.loanProduct - .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct - .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); + .getProductInterestRecalculationDetails()); this.loanInterestRecalculationDetails.updateLoan(this); } @@ -2299,7 +2243,7 @@ public class Loan extends AbstractPersistable { } public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map actualChanges, - final ScheduleGeneratorDTO scheduleGeneratorDTO,final PaymentDetail paymentDetail) { + final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail) { final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); @@ -2330,7 +2274,7 @@ public class Loan extends AbstractPersistable { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); - handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser,paymentDetail); + handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser, paymentDetail); updateLoanSummaryDerivedFields(); final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); @@ -2380,8 +2324,13 @@ public class Loan extends AbstractPersistable { this.loanTermVariations.add(loanVariationTerms); } - if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged || rescheduledRepaymentDate != null) { - regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); + if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged + || rescheduledRepaymentDate != null) { + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); + } else { + regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); + } } } @@ -2592,7 +2541,7 @@ public class Loan extends AbstractPersistable { updateLoanSchedule(loanSchedule, currentUser); Set charges = this.charges(); for (LoanCharge loanCharge : charges) { - recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod()); + recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod()); } } @@ -2633,7 +2582,8 @@ public class Loan extends AbstractPersistable { return interestRate; } - private void handleDisbursementTransaction(final LocalDate disbursedOn, final LocalDateTime createdDate, final AppUser currentUser, final PaymentDetail paymentDetail) { + private void handleDisbursementTransaction(final LocalDate disbursedOn, final LocalDateTime createdDate, final AppUser currentUser, + final PaymentDetail paymentDetail) { // add repayment transaction to track incoming money from client to mfi // for (charges due at time of disbursement) @@ -2651,14 +2601,14 @@ public class Loan extends AbstractPersistable { **/ Money disbursentMoney = Money.zero(getCurrency()); - final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, paymentDetail, disbursedOn, - null, createdDate, currentUser); + final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, paymentDetail, + disbursedOn, null, createdDate, currentUser); final Integer installmentNumber = null; for (final LoanCharge charge : charges()) { Date actualDisbursementDate = getActualDisbursementDate(charge); - if ((charge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue() - && disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null - && !charge.isWaived() && !charge.isFullyPaid()) + if ((charge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue() + && disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null && !charge.isWaived() && !charge + .isFullyPaid()) || (charge.getCharge().getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue() && disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null && !charge.isWaived() && !charge.isFullyPaid())) { @@ -3088,6 +3038,18 @@ public class Loan extends AbstractPersistable { return installment; } + private List retreiveListOfIncomePostingTransactions() { + final List incomePostTransactions = new ArrayList<>(); + for (final LoanTransaction transaction : this.loanTransactions) { + if (transaction.isNotReversed() && transaction.isIncomePosting()) { + incomePostTransactions.add(transaction); + } + } + final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); + Collections.sort(incomePostTransactions, transactionComparator); + return incomePostTransactions; + } + private List retreiveListOfTransactionsPostDisbursement() { final List repaymentsOrWaivers = new ArrayList<>(); for (final LoanTransaction transaction : this.loanTransactions) { @@ -3104,8 +3066,8 @@ public class Loan extends AbstractPersistable { final List repaymentsOrWaivers = new ArrayList<>(); for (final LoanTransaction transaction : this.loanTransactions) { if (transaction.isNotReversed() - && !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement() || transaction - .isNonMonetaryTransaction())) { + && !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement() + || transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) { repaymentsOrWaivers.add(transaction); } } @@ -3169,6 +3131,71 @@ public class Loan extends AbstractPersistable { LoanStatus.fromInt(this.loanStatus)); this.loanStatus = statusEnum.getValue(); } + processIncomeAccrualTransactionOnLoanClosure(); + } + + private void processIncomeAccrualTransactionOnLoanClosure() { + if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction() + && this.status().isClosedObligationsMet()) { + Date closedDate = this.getClosedOnDate(); + LocalDate closedLocalDate = new LocalDate(closedDate); + reverseTransactionsOnOrAfter(retreiveListOfIncomePostingTransactions(), closedDate); + reverseTransactionsOnOrAfter(retreiveListOfAccrualTransactions(), closedDate); + HashMap cumulativeIncomeFromInstallments = new HashMap<>(); + determineCumulativeIncomeFromInstallments(cumulativeIncomeFromInstallments); + HashMap cumulativeIncomeFromIncomePosting = new HashMap<>(); + determineCumulativeIncomeDetails(retreiveListOfIncomePostingTransactions(), cumulativeIncomeFromIncomePosting); + BigDecimal interestToPost = cumulativeIncomeFromInstallments.get("interest").subtract( + cumulativeIncomeFromIncomePosting.get("interest")); + BigDecimal feeToPost = cumulativeIncomeFromInstallments.get("fee").subtract(cumulativeIncomeFromIncomePosting.get("fee")); + BigDecimal penaltyToPost = cumulativeIncomeFromInstallments.get("penalty").subtract( + cumulativeIncomeFromIncomePosting.get("penalty")); + BigDecimal amountToPost = interestToPost.add(feeToPost).add(penaltyToPost); + LoanTransaction finalIncomeTransaction = LoanTransaction.incomePosting(this, this.getOffice(), closedDate, amountToPost, + interestToPost, feeToPost, penaltyToPost, null); + this.loanTransactions.add(finalIncomeTransaction); + if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { + List updatedAccrualTransactions = retreiveListOfAccrualTransactions(); + LocalDate lastAccruedDate = this.getDisbursementDate(); + if (updatedAccrualTransactions != null && updatedAccrualTransactions.size() > 0) { + lastAccruedDate = updatedAccrualTransactions.get(updatedAccrualTransactions.size() - 1).getTransactionDate(); + } + HashMap feeDetails = new HashMap<>(); + determineFeeDetails(lastAccruedDate, closedLocalDate, feeDetails); + LoanTransaction finalAccrual = LoanTransaction.accrueTransaction(this, this.getOffice(), closedLocalDate, amountToPost, + interestToPost, feeToPost, penaltyToPost, null); + updateLoanChargesPaidBy(finalAccrual, feeDetails, null); + this.loanTransactions.add(finalAccrual); + } + } + } + + private void determineCumulativeIncomeFromInstallments(HashMap cumulativeIncomeFromInstallments) { + BigDecimal interest = BigDecimal.ZERO; + BigDecimal fee = BigDecimal.ZERO; + BigDecimal penalty = BigDecimal.ZERO; + for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { + interest = interest.add(installment.getInterestCharged(getCurrency()).getAmount()); + fee = fee.add(installment.getFeeChargesCharged(getCurrency()).getAmount()); + penalty = penalty.add(installment.getPenaltyChargesCharged(getCurrency()).getAmount()); + } + cumulativeIncomeFromInstallments.put("interest", interest); + cumulativeIncomeFromInstallments.put("fee", fee); + cumulativeIncomeFromInstallments.put("penalty", penalty); + } + + private void determineCumulativeIncomeDetails(Collection transactions, HashMap incomeDetailsMap) { + BigDecimal interest = BigDecimal.ZERO; + BigDecimal fee = BigDecimal.ZERO; + BigDecimal penalty = BigDecimal.ZERO; + for (LoanTransaction transaction : transactions) { + interest = interest.add(transaction.getInterestPortion(getCurrency()).getAmount()); + fee = fee.add(transaction.getFeeChargesPortion(getCurrency()).getAmount()); + penalty = penalty.add(transaction.getPenaltyChargesPortion(getCurrency()).getAmount()); + } + incomeDetailsMap.put("interest", interest); + incomeDetailsMap.put("fee", fee); + incomeDetailsMap.put("penalty", penalty); } private void handleLoanOverpayment(final LoanLifecycleStateMachine loanLifecycleStateMachine) { @@ -4566,7 +4593,7 @@ public class Loan extends AbstractPersistable { private LocalDate getLastUserTransactionDate() { LocalDate currentTransactionDate = getDisbursementDate(); for (final LoanTransaction previousTransaction : this.loanTransactions) { - if (!(previousTransaction.isReversed() || previousTransaction.isAccrual())) { + if (!(previousTransaction.isReversed() || previousTransaction.isAccrual() || previousTransaction.isIncomePosting())) { if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) { currentTransactionDate = previousTransaction.getTransactionDate(); } @@ -4982,6 +5009,179 @@ public class Loan extends AbstractPersistable { } processPostDisbursementTransactions(); + processIncomeTransactions(currentUser); + } + + private void updateLoanChargesPaidBy(LoanTransaction accrual, HashMap feeDetails, + LoanRepaymentScheduleInstallment installment) { + @SuppressWarnings("unchecked") + List loanCharges = (List) feeDetails.get("loanCharges"); + @SuppressWarnings("unchecked") + List loanInstallmentCharges = (List) feeDetails.get("loanInstallmentCharges"); + if (loanCharges != null) { + for (LoanCharge loanCharge : loanCharges) { + Integer installmentNumber = null == installment ? null : installment.getInstallmentNumber(); + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrual, loanCharge, loanCharge.getAmount(getCurrency()) + .getAmount(), installmentNumber); + accrual.getLoanChargesPaid().add(loanChargePaidBy); + } + } + if (loanInstallmentCharges != null) { + for (LoanInstallmentCharge loanInstallmentCharge : loanInstallmentCharges) { + Integer installmentNumber = null == loanInstallmentCharge.getInstallment() ? null : loanInstallmentCharge.getInstallment() + .getInstallmentNumber(); + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrual, loanInstallmentCharge.getLoancharge(), + loanInstallmentCharge.getAmount(getCurrency()).getAmount(), installmentNumber); + accrual.getLoanChargesPaid().add(loanChargePaidBy); + } + } + } + + public void processIncomeTransactions(AppUser currentUser) { + if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()) { + LocalDate lastCompoundingDate = this.getDisbursementDate(); + List compoundingDetails = extractInterestRecalculationAdditionalDetails(); + List incomeTransactions = retreiveListOfIncomePostingTransactions(); + List accrualTransactions = retreiveListOfAccrualTransactions(); + for (LoanInterestRecalcualtionAdditionalDetails compoundingDetail : compoundingDetails) { + if (!compoundingDetail.getEffectiveDate().isBefore(DateUtils.getLocalDateOfTenant())) { + break; + } + LoanTransaction incomeTransaction = getTransactionForDate(incomeTransactions, compoundingDetail.getEffectiveDate()); + LoanTransaction accrualTransaction = getTransactionForDate(accrualTransactions, compoundingDetail.getEffectiveDate()); + addUpdateIncomeAndAccrualTransaction(compoundingDetail, lastCompoundingDate, currentUser, incomeTransaction, + accrualTransaction); + lastCompoundingDate = compoundingDetail.getEffectiveDate(); + } + } + } + + private void reverseTransactionsOnOrAfter(List transactions, Date date) { + LocalDate refDate = new LocalDate(date); + for (LoanTransaction loanTransaction : transactions) { + if (!loanTransaction.getTransactionDate().isBefore(refDate)) { + loanTransaction.reverse(); + } + } + } + + private void addUpdateIncomeAndAccrualTransaction(LoanInterestRecalcualtionAdditionalDetails compoundingDetail, + LocalDate lastCompoundingDate, AppUser currentUser, LoanTransaction existingIncomeTransaction, + LoanTransaction existingAccrualTransaction) { + BigDecimal interest = BigDecimal.ZERO; + BigDecimal fee = BigDecimal.ZERO; + BigDecimal penalties = BigDecimal.ZERO; + HashMap feeDetails = new HashMap<>(); + + if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().equals( + InterestRecalculationCompoundingMethod.INTEREST)) { + interest = compoundingDetail.getAmount(); + } else if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().equals( + InterestRecalculationCompoundingMethod.FEE)) { + determineFeeDetails(lastCompoundingDate, compoundingDetail.getEffectiveDate(), feeDetails); + fee = (BigDecimal) feeDetails.get("fee"); + penalties = (BigDecimal) feeDetails.get("penalties"); + } else if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().equals( + InterestRecalculationCompoundingMethod.INTEREST_AND_FEE)) { + determineFeeDetails(lastCompoundingDate, compoundingDetail.getEffectiveDate(), feeDetails); + fee = (BigDecimal) feeDetails.get("fee"); + penalties = (BigDecimal) feeDetails.get("penalties"); + interest = compoundingDetail.getAmount().subtract(fee).subtract(penalties); + } + + if (existingIncomeTransaction == null) { + LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate() + .toDate(), compoundingDetail.getAmount(), interest, fee, penalties, currentUser); + this.loanTransactions.add(transaction); + } else if (existingIncomeTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) { + existingIncomeTransaction.reverse(); + LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate() + .toDate(), compoundingDetail.getAmount(), interest, fee, penalties, currentUser); + this.loanTransactions.add(transaction); + } + + if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { + if (existingAccrualTransaction == null) { + LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(), + compoundingDetail.getAmount(), interest, fee, penalties, currentUser); + updateLoanChargesPaidBy(accrual, feeDetails, null); + this.loanTransactions.add(accrual); + } else if (existingAccrualTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) { + existingAccrualTransaction.reverse(); + LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(), + compoundingDetail.getAmount(), interest, fee, penalties, currentUser); + updateLoanChargesPaidBy(accrual, feeDetails, null); + this.loanTransactions.add(accrual); + } + } + updateLoanOutstandingBalaces(); + } + + private void determineFeeDetails(LocalDate fromDate, LocalDate toDate, HashMap feeDetails) { + BigDecimal fee = BigDecimal.ZERO; + BigDecimal penalties = BigDecimal.ZERO; + + List installments = new ArrayList<>(); + for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { + if (loanRepaymentScheduleInstallment.getDueDate().isAfter(fromDate) + && !loanRepaymentScheduleInstallment.getDueDate().isAfter(toDate)) { + installments.add(loanRepaymentScheduleInstallment.getInstallmentNumber()); + } + } + + List loanCharges = new ArrayList<>(); + List loanInstallmentCharges = new ArrayList<>(); + for (LoanCharge loanCharge : this.charges()) { + if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, toDate)) { + if (loanCharge.isPenaltyCharge() && !loanCharge.isInstalmentFee()) { + penalties = penalties.add(loanCharge.amount()); + loanCharges.add(loanCharge); + } else if (!loanCharge.isInstalmentFee()) { + fee = fee.add(loanCharge.amount()); + loanCharges.add(loanCharge); + } + } else if (loanCharge.isInstalmentFee()) { + for (LoanInstallmentCharge installmentCharge : loanCharge.installmentCharges()) { + if (installments.contains(installmentCharge.getRepaymentInstallment().getInstallmentNumber())) { + fee = fee.add(installmentCharge.getAmount()); + loanInstallmentCharges.add(installmentCharge); + } + } + } + } + + feeDetails.put("fee", fee); + feeDetails.put("penalties", penalties); + feeDetails.put("loanCharges", loanCharges); + feeDetails.put("loanInstallmentCharges", loanInstallmentCharges); + } + + private LoanTransaction getTransactionForDate(List transactions, LocalDate effectiveDate) { + for (LoanTransaction loanTransaction : transactions) { + if (loanTransaction.getTransactionDate().isEqual(effectiveDate)) { return loanTransaction; } + } + return null; + } + + private List extractInterestRecalculationAdditionalDetails() { + List retDetails = new ArrayList<>(); + if (null != this.repaymentScheduleInstallments && this.repaymentScheduleInstallments.size() > 0) { + Iterator installmentsItr = this.repaymentScheduleInstallments.iterator(); + while (installmentsItr.hasNext()) { + LoanRepaymentScheduleInstallment installment = installmentsItr.next(); + if (null != installment.getLoanCompoundingDetails()) { + retDetails.addAll(installment.getLoanCompoundingDetails()); + } + } + } + Collections.sort(retDetails, new Comparator() { + + @Override + public int compare(LoanInterestRecalcualtionAdditionalDetails first, LoanInterestRecalcualtionAdditionalDetails second) { + return first.getEffectiveDate().compareTo(second.getEffectiveDate()); + } + }); + return retDetails; } public void processPostDisbursementTransactions() { @@ -5044,21 +5244,27 @@ public class Loan extends AbstractPersistable { public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO) { final Integer loanTermFrequency = this.termFrequency; final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); - final NthDayType nthDayType = NthDayType.fromInt(this.repaymentFrequencyNthDayType); - final DayOfWeekType dayOfWeekType = DayOfWeekType.fromInt(this.repaymentFrequencyDayOfWeekType); + NthDayType nthDayType = null; + DayOfWeekType dayOfWeekType = null; final List disbursementData = new ArrayList<>(); for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) { disbursementData.add(disbursementDetails.toData()); } + Calendar calendar = scheduleGeneratorDTO.getCalendar(); + if (calendar != null) { + nthDayType = CalendarUtils.getRepeatsOnNthDayOfMonth(calendar.getRecurrence()); + dayOfWeekType = DayOfWeekType.fromInt(CalendarUtils.getRepeatsOnDay(calendar.getRecurrence()).getValue()); + } + HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); CalendarInstance restCalendarInstance = null; CalendarInstance compoundingCalendarInstance = null; RecalculationFrequencyType recalculationFrequencyType = null; InterestRecalculationCompoundingMethod compoundingMethod = null; RecalculationFrequencyType compoundingFrequencyType = null; LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - Calendar calendar = null; CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + boolean allowCompoundingOnEod = false; if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation(); compoundingCalendarInstance = scheduleGeneratorDTO.getCompoundingCalendarInstance(); @@ -5066,6 +5272,8 @@ public class Loan extends AbstractPersistable { compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); + allowCompoundingOnEod = this.loanInterestRecalculationDetails.allowCompoundingOnEod(); + calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper(); } calendar = scheduleGeneratorDTO.getCalendar(); calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper(); @@ -5075,7 +5283,7 @@ public class Loan extends AbstractPersistable { List loanTermVariations = new ArrayList<>(); annualNominalInterestRate = constructLoanTermVariations(floatingRateDTO, annualNominalInterestRate, loanTermVariations); LocalDate interestChargedFromDate = getInterestChargedFromDate(); - if(interestChargedFromDate == null && scheduleGeneratorDTO.isInterestChargedFromDateAsDisbursementDateEnabled()){ + if (interestChargedFromDate == null && scheduleGeneratorDTO.isInterestChargedFromDateAsDisbursementDateEnabled()) { interestChargedFromDate = getDisbursementDate(); } @@ -5087,7 +5295,7 @@ public class Loan extends AbstractPersistable { this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, calendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper, - scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth()); + scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), holidayDetailDTO, allowCompoundingOnEod); return loanApplicationTerms; } @@ -5105,6 +5313,7 @@ public class Loan extends AbstractPersistable { Money penaltyCharges = Money.zero(loanCurrency()); Money totalPrincipal = Money.zero(loanCurrency()); Money totalInterest = Money.zero(loanCurrency()); + final List compoundingDetails = null; for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(loanCurrency())); totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency())); @@ -5112,7 +5321,7 @@ public class Loan extends AbstractPersistable { penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency())); } return new LoanRepaymentScheduleInstallment(null, 0, LocalDate.now(), LocalDate.now(), totalPrincipal.getAmount(), - totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false); + totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails); } public List fetchRepaymentScheduleInstallments() { @@ -5141,11 +5350,17 @@ public class Loan extends AbstractPersistable { Money outstanding = Money.zero(getCurrency()); List loanTransactions = retreiveListOfTransactionsExcludeAccruals(); for (LoanTransaction loanTransaction : loanTransactions) { - if (loanTransaction.isDisbursement()) { + if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) { outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency())); loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount()); } else { - outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency())); + if (this.loanInterestRecalculationDetails != null + && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction() + && !loanTransaction.isRepaymentAtDisbursement()) { + outstanding = outstanding.minus(loanTransaction.getAmount(getCurrency())); + } else { + outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency())); + } loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount()); } } @@ -5252,7 +5467,8 @@ public class Loan extends AbstractPersistable { @SuppressWarnings({ "unused" }) public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency, final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, - final FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays) { + final FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays, + final HolidayDetailDTO holidayDetailDTO) { LoanProduct loanProduct = loanProduct(); // LoanProductRelatedDetail loanProductRelatedDetail = // getLoanRepaymentScheduleDetail(); @@ -5260,8 +5476,15 @@ public class Loan extends AbstractPersistable { final Integer loanTermFrequency = getTermFrequency(); final PeriodFrequencyType loanTermPeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(); - final NthDayType nthDayType = NthDayType.fromInt(this.repaymentFrequencyNthDayType); - final DayOfWeekType dayOfWeekType = DayOfWeekType.fromInt(this.repaymentFrequencyDayOfWeekType); + NthDayType nthDayType = null; + DayOfWeekType dayOfWeekType = null; + if (loanCalendar != null) { + nthDayType = CalendarUtils.getRepeatsOnNthDayOfMonth(loanCalendar.getRecurrence()); + CalendarWeekDaysType getRepeatsOnDay = CalendarUtils.getRepeatsOnDay(loanCalendar.getRecurrence()); + Integer getRepeatsOnDayValue = null; + if (getRepeatsOnDay != null) getRepeatsOnDayValue = getRepeatsOnDay.getValue(); + if (getRepeatsOnDayValue != null) dayOfWeekType = DayOfWeekType.fromInt(getRepeatsOnDayValue); + } final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments(); final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery(); @@ -5304,7 +5527,7 @@ public class Loan extends AbstractPersistable { final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance(); final List disbursementData = getDisbursmentData(); - + CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; if (loanCalendar != null) { Set calendarHistory = loanCalendar.getCalendarHistory(); @@ -5315,11 +5538,13 @@ public class Loan extends AbstractPersistable { InterestRecalculationCompoundingMethod compoundingMethod = null; RecalculationFrequencyType compoundingFrequencyType = null; LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + boolean allowCompoundingOnEod = false; if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType(); compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); + allowCompoundingOnEod = this.loanInterestRecalculationDetails.allowCompoundingOnEod(); } List loanTermVariations = new ArrayList<>(); @@ -5332,7 +5557,7 @@ public class Loan extends AbstractPersistable { this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, - calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst); + calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst, holidayDetailDTO, allowCompoundingOnEod); } /** @@ -5376,22 +5601,6 @@ public class Loan extends AbstractPersistable { return isEnabled; } - public Integer getRepaymentFrequencyNthDayType() { - return this.repaymentFrequencyNthDayType; - } - - public void setRepaymentFrequencyNthDayType(Integer repaymentFrequencyNthDayType) { - this.repaymentFrequencyNthDayType = repaymentFrequencyNthDayType; - } - - public Integer getRepaymentFrequencyDayOfWeekType() { - return this.repaymentFrequencyDayOfWeekType; - } - - public void setRepaymentFrequencyDayOfWeekType(Integer repaymentFrequencyDayOfWeekType) { - this.repaymentFrequencyDayOfWeekType = repaymentFrequencyDayOfWeekType; - } - public String getAccountNumber() { return this.accountNumber; } @@ -5597,8 +5806,10 @@ public class Loan extends AbstractPersistable { updateLoanToLastDisbursalState(actualDisbursementDate); for (Iterator iterator = this.loanTermVariations.iterator(); iterator.hasNext();) { LoanTermVariations loanTermVariations = iterator.next(); - if (loanTermVariations.getTermType().isDueDateVariation() && loanTermVariations.fetchDateValue().isAfter(actualDisbursementDate) || - loanTermVariations.getTermType().isEMIAmountVariation() && loanTermVariations.getTermApplicableFrom().equals(actualDisbursementDate.toDate()) + if (loanTermVariations.getTermType().isDueDateVariation() + && loanTermVariations.fetchDateValue().isAfter(actualDisbursementDate) + || loanTermVariations.getTermType().isEMIAmountVariation() + && loanTermVariations.getTermApplicableFrom().equals(actualDisbursementDate.toDate()) || loanTermVariations.getTermApplicableFrom().after(actualDisbursementDate.toDate())) { iterator.remove(); } @@ -5722,7 +5933,7 @@ public class Loan extends AbstractPersistable { } return nextRepaymentDate; } - + public BigDecimal getDerivedAmountForCharge(LoanCharge loanCharge) { BigDecimal amount = BigDecimal.ZERO; if (isMultiDisburmentLoan() && (loanCharge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue())) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java index 5a2241bad..f1f8c4e6e 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java @@ -288,4 +288,10 @@ public class LoanInstallmentCharge extends AbstractPersistable { return amountToDeductOnThisCharge; } + public LoanCharge getLoancharge() { + return this.loancharge; + } + public LoanRepaymentScheduleInstallment getInstallment() { + return this.installment; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalcualtionAdditionalDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalcualtionAdditionalDetails.java new file mode 100644 index 000000000..f37476025 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalcualtionAdditionalDetails.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.loanaccount.domain; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.joda.time.LocalDate; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_loan_interest_recalculation_additional_details") +public class LoanInterestRecalcualtionAdditionalDetails extends AbstractPersistable { + + @Temporal(TemporalType.DATE) + @Column(name = "effective_date") + private Date effectiveDate; + + @Column(name = "amount", scale = 6, precision = 19, nullable = false) + private BigDecimal amount; + + protected LoanInterestRecalcualtionAdditionalDetails() { + + } + + public LoanInterestRecalcualtionAdditionalDetails(final LocalDate effectiveDate, final BigDecimal amount) { + if (effectiveDate != null) { + this.effectiveDate = effectiveDate.toDate(); + } + this.amount = amount; + } + + public LocalDate getEffectiveDate() { + return new LocalDate(this.effectiveDate); + } + + public BigDecimal getAmount() { + return this.amount; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java index fe071f572..a56c004b1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java @@ -18,23 +18,16 @@ */ package org.apache.fineract.portfolio.loanaccount.domain; -import java.util.Date; -import java.util.Map; - import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; +import org.apache.fineract.portfolio.loanproduct.domain.LoanProductInterestRecalculationDetails; import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; -import org.joda.time.LocalDate; import org.springframework.data.jpa.domain.AbstractPersistable; /** @@ -70,9 +63,14 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable @Column(name = "rest_frequency_interval", nullable = false) private Integer restInterval; - @Temporal(TemporalType.DATE) - @Column(name = "rest_freqency_date") - private Date restFrequencyDate; + @Column(name = "rest_frequency_nth_day_enum", nullable = true) + private Integer restFrequencyNthDay; + + @Column(name = "rest_frequency_weekday_enum", nullable = true) + private Integer restFrequencyWeekday; + + @Column(name = "rest_frequency_on_day", nullable = true) + private Integer restFrequencyOnDay; @Column(name = "compounding_frequency_type_enum", nullable = true) private Integer compoundingFrequencyType; @@ -80,78 +78,63 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable @Column(name = "compounding_frequency_interval", nullable = true) private Integer compoundingInterval; - @Temporal(TemporalType.DATE) - @Column(name = "compounding_freqency_date") - private Date compoundingFrequencyDate; + @Column(name = "compounding_frequency_nth_day_enum", nullable = true) + private Integer compoundingFrequencyNthDay; + @Column(name = "compounding_frequency_weekday_enum", nullable = true) + private Integer compoundingFrequencyWeekday; + @Column(name = "compounding_frequency_on_day", nullable = true) + private Integer compoundingFrequencyOnDay; + + @Column(name = "is_compounding_to_be_posted_as_transaction") + private Boolean isCompoundingToBePostedAsTransaction; + @Column(name = "allow_compounding_on_eod") + private Boolean allowCompoundingOnEod; protected LoanInterestRecalculationDetails() { // Default constructor for jpa repository } private LoanInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod, - final Integer restFrequencyType, final Integer restInterval, final Date restFrequencyDate, Integer compoundingFrequencyType, - Integer compoundingInterval, Date compoundingFrequencyDate) { + final Integer restFrequencyType, final Integer restInterval, final Integer restFrequencyNthDay, Integer restFrequencyWeekday, + Integer restFrequencyOnDay, Integer compoundingFrequencyType, Integer compoundingInterval, Integer compoundingFrequencyNthDay, + Integer compoundingFrequencyWeekday, Integer compoundingFrequencyOnDay, final boolean isCompoundingToBePostedAsTransaction, + final boolean allowCompoundingOnEod) { this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; this.rescheduleStrategyMethod = rescheduleStrategyMethod; - this.restFrequencyDate = restFrequencyDate; + this.restFrequencyNthDay = restFrequencyNthDay; + this.restFrequencyWeekday = restFrequencyWeekday; + this.restFrequencyOnDay = restFrequencyOnDay; this.restFrequencyType = restFrequencyType; this.restInterval = restInterval; - this.compoundingFrequencyDate = compoundingFrequencyDate; + this.compoundingFrequencyNthDay = compoundingFrequencyNthDay; + this.compoundingFrequencyWeekday = compoundingFrequencyWeekday; + this.compoundingFrequencyOnDay = compoundingFrequencyOnDay; this.compoundingFrequencyType = compoundingFrequencyType; this.compoundingInterval = compoundingInterval; + this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; + this.allowCompoundingOnEod = allowCompoundingOnEod; } - public static LoanInterestRecalculationDetails createFrom(final Integer interestRecalculationCompoundingMethod, - final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval, - final Date restFrequencyDate, final Integer compoundingFrequencyType, final Integer compoundingInterval, - final Date compoundingFrequencyDate) { - return new LoanInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, restFrequencyType, - restInterval, restFrequencyDate, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate); + public static LoanInterestRecalculationDetails createFrom( + final LoanProductInterestRecalculationDetails loanProductInterestRecalculationDetails) { + return new LoanInterestRecalculationDetails(loanProductInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(), + loanProductInterestRecalculationDetails.getRescheduleStrategyMethod(), loanProductInterestRecalculationDetails + .getRestFrequencyType().getValue(), loanProductInterestRecalculationDetails.getRestInterval(), + loanProductInterestRecalculationDetails.getRestFrequencyNthDay(), + loanProductInterestRecalculationDetails.getRestFrequencyWeekday(), + loanProductInterestRecalculationDetails.getRestFrequencyOnDay(), loanProductInterestRecalculationDetails + .getCompoundingFrequencyType().getValue(), loanProductInterestRecalculationDetails.getCompoundingInterval(), + loanProductInterestRecalculationDetails.getCompoundingFrequencyNthDay(), + loanProductInterestRecalculationDetails.getCompoundingFrequencyWeekday(), + loanProductInterestRecalculationDetails.getCompoundingFrequencyOnDay(), + loanProductInterestRecalculationDetails.getIsCompoundingToBePostedAsTransaction(), + loanProductInterestRecalculationDetails.allowCompoundingOnEod()); } public void updateLoan(final Loan loan) { this.loan = loan; } - public void update(final JsonCommand command, final Map actualChanges) { - if (command.isChangeInLocalDateParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName, - getRestFrequencyLocalDate())) { - final String dateFormatAsInput = command.dateFormat(); - final String localeAsInput = command.locale(); - final String valueAsInput = command.stringValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); - actualChanges.put(LoanProductConstants.recalculationRestFrequencyDateParamName, valueAsInput); - actualChanges.put("dateFormat", dateFormatAsInput); - actualChanges.put("locale", localeAsInput); - - final LocalDate newValue = command.localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); - 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(); - } - } - } - public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() { return InterestRecalculationCompoundingMethod.fromInt(this.interestRecalculationCompoundingMethod); } @@ -160,14 +143,6 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable return LoanRescheduleStrategyMethod.fromInt(this.rescheduleStrategyMethod); } - public LocalDate getRestFrequencyLocalDate() { - LocalDate recurrenceOnLocalDate = null; - if (this.restFrequencyDate != null) { - recurrenceOnLocalDate = new LocalDate(this.restFrequencyDate); - } - return recurrenceOnLocalDate; - } - public RecalculationFrequencyType getRestFrequencyType() { return RecalculationFrequencyType.fromInt(this.restFrequencyType); } @@ -176,14 +151,6 @@ public class LoanInterestRecalculationDetails 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); } @@ -191,4 +158,36 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable public Integer getCompoundingInterval() { return this.compoundingInterval; } + + public Integer getRestFrequencyNthDay() { + return this.restFrequencyNthDay; + } + + public Integer getRestFrequencyWeekday() { + return this.restFrequencyWeekday; + } + + public Integer getRestFrequencyOnDay() { + return this.restFrequencyOnDay; + } + + public Integer getCompoundingFrequencyNthDay() { + return this.compoundingFrequencyNthDay; + } + + public Integer getCompoundingFrequencyWeekday() { + return this.compoundingFrequencyWeekday; + } + + public Integer getCompoundingFrequencyOnDay() { + return this.compoundingFrequencyOnDay; + } + + public boolean isCompoundingToBePostedAsTransaction() { + return null == this.isCompoundingToBePostedAsTransaction ? false : this.isCompoundingToBePostedAsTransaction; + } + + public boolean allowCompoundingOnEod() { + return this.allowCompoundingOnEod; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 73aedb695..711a3130c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -19,13 +19,17 @@ package org.apache.fineract.portfolio.loanaccount.domain; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Comparator; import java.util.Date; +import java.util.List; +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -34,6 +38,8 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.useradministration.domain.AppUser; +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; import org.joda.time.LocalDate; @Entity @@ -125,6 +131,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus @Column(name = "recalculated_interest_component", nullable = false) private boolean recalculatedInterestComponent; + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "loan_repayment_schedule_id", referencedColumnName = "id", nullable = false) + private List loanCompoundingDetails = new ArrayList<>(); protected LoanRepaymentScheduleInstallment() { this.installmentNumber = null; this.fromDate = null; @@ -134,7 +144,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges, - final BigDecimal penaltyCharges, boolean recalculatedInterestComponent) { + final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent, + final List compoundingDetails) { this.loan = loan; this.installmentNumber = installmentNumber; this.fromDate = fromDate.toDateTimeAtStartOfDay().toDate(); @@ -145,6 +156,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus this.penaltyCharges = defaultToNullIfZero(penaltyCharges); this.obligationsMet = false; this.recalculatedInterestComponent = recalculatedInterestComponent; + this.loanCompoundingDetails = compoundingDetails; } public LoanRepaymentScheduleInstallment(final Loan loan) { @@ -770,4 +782,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus public Money getDue(MonetaryCurrency currency) { return getPrincipal(currency).plus(getInterestCharged(currency)).plus(getFeeChargesCharged(currency)).plus(getPenaltyChargesCharged(currency)); } + public List getLoanCompoundingDetails() { + return this.loanCompoundingDetails; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 6d4783af3..8172d0b96 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -146,6 +146,19 @@ public final class LoanTransaction extends AbstractPersistable { this.appUser = null; } + public static LoanTransaction incomePosting(final Loan loan, final Office office, final Date dateOf, + final BigDecimal amount, final BigDecimal interestPortion, final BigDecimal feeChargesPortion, + final BigDecimal penaltyChargesPortion, final AppUser appUser){ + final Integer typeOf = LoanTransactionType.INCOME_POSTING.getValue(); + final BigDecimal principalPortion = BigDecimal.ZERO; + final BigDecimal overPaymentPortion = BigDecimal.ZERO; + final boolean reversed = false; + final PaymentDetail paymentDetail = null; + final String externalId = null; + final LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); + return new LoanTransaction(loan, office, typeOf, dateOf, amount, principalPortion, interestPortion, feeChargesPortion, + penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId, createdDate, appUser); + } public static LoanTransaction disbursement(final Office office, final Money amount, final PaymentDetail paymentDetail, final LocalDate disbursementDate, final String externalId, final LocalDateTime createdDate, final AppUser appUser) { return new LoanTransaction(null, office, LoanTransactionType.DISBURSEMENT, paymentDetail, amount.getAmount(), disbursementDate, @@ -199,6 +212,19 @@ public final class LoanTransaction extends AbstractPersistable { principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId, createdDate, appUser); } + public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount, + final BigDecimal interestPortion, final BigDecimal feeChargesPortion, + final BigDecimal penaltyChargesPortion, final AppUser appUser) { + BigDecimal principalPortion = null; + BigDecimal overPaymentPortion = null; + boolean reversed = false; + PaymentDetail paymentDetail = null; + String externalId = null; + LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); + return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf.toDate(), amount, + principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId, + createdDate, appUser); + } public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate, final LocalDateTime createdDate, final AppUser appUser) { @@ -461,6 +487,9 @@ public final class LoanTransaction extends AbstractPersistable { return !isRepayment(); } + public boolean isIncomePosting() { + return LoanTransactionType.INCOME_POSTING.equals(getTypeOf()) && isNotReversed(); + } public boolean isDisbursement() { return LoanTransactionType.DISBURSEMENT.equals(getTypeOf()) && isNotReversed(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java index 2e1ae0d08..d8985613d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java @@ -51,7 +51,8 @@ public enum LoanTransactionType { REJECT_TRANSFER(15, "loanTransactionType.rejectTransfer"), // REFUND(16, "loanTransactionType.refund"), // CHARGE_PAYMENT(17, "loanTransactionType.chargePayment"), // - REFUND_FOR_ACTIVE_LOAN(18, "loanTransactionType.refund"); + REFUND_FOR_ACTIVE_LOAN(18, "loanTransactionType.refund"), // + INCOME_POSTING(19,"loanTransactionType.incomePosting"); private final Integer value; private final String code; @@ -126,6 +127,9 @@ public enum LoanTransactionType { case 18: loanTransactionType = LoanTransactionType.REFUND_FOR_ACTIVE_LOAN; break; + case 19: + loanTransactionType = LoanTransactionType.INCOME_POSTING; + break; default: loanTransactionType = LoanTransactionType.INVALID; break; @@ -172,4 +176,7 @@ public enum LoanTransactionType { public boolean isRefundForActiveLoan() { return this.value.equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue()); } + public boolean isIncomePosting() { + return this.value.equals(LoanTransactionType.INCOME_POSTING.getValue()); + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java index 679964abb..446c64ec2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/data/LoanScheduleParams.java @@ -69,8 +69,19 @@ public class LoanScheduleParams { // date(after applying compounding frequency) // from when these amounts will effect the outstanding balance for // interest calculation - private final TreeMap compoundingMap; - private final Map> compoundingDateVariations = new HashMap<>(); + private final Map compoundingMap; + private final Map> compoundingDateVariations = new HashMap<>(); + private Money unCompoundedAmount; + private Money compoundedInLastInstallment; + + + public Money getCompoundedInLastInstallment() { + return this.compoundedInLastInstallment; + } + + public void setCompoundedInLastInstallment(Money compoundedInLastInstallment) { + this.compoundedInLastInstallment = compoundedInLastInstallment; + } // disbursement map for tranche details(will added to outstanding // balance as per the start date) @@ -94,7 +105,7 @@ public class LoanScheduleParams { final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal, final Map principalPortionMap, - final Map latePaymentMap, final TreeMap compoundingMap, + final Map latePaymentMap, final Map compoundingMap, final Money unCompoundedAmount, final Map disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest, final List installments, final Collection recalculationDetails, @@ -115,6 +126,7 @@ public class LoanScheduleParams { this.principalPortionMap = principalPortionMap; this.latePaymentMap = latePaymentMap; this.compoundingMap = compoundingMap; + this.unCompoundedAmount = unCompoundedAmount; this.disburseDetailMap = disburseDetailMap; this.principalToBeScheduled = principalToBeScheduled; this.outstandingBalance = outstandingBalance; @@ -126,6 +138,9 @@ public class LoanScheduleParams { this.partialUpdate = partialUpdate; this.currency = currency; this.applyInterestRecalculation = applyInterestRecalculation; + if (this.currency != null) { + this.compoundedInLastInstallment = Money.zero(this.currency); + } } public static LoanScheduleParams createLoanScheduleParamsForPartialUpdate(final int periodNumber, final int instalmentNumber, @@ -133,8 +148,8 @@ public class LoanScheduleParams { final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal, final Map principalPortionMap, final Map latePaymentMap, - final TreeMap compoundingMap, final Map disburseDetailMap, Money principalToBeScheduled, - final Money outstandingBalance, final Money outstandingBalanceAsPerRest, + final Map compoundingMap, Money unCompoundedAmount, final Map disburseDetailMap, + final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest, final List installments, final Collection recalculationDetails, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) { @@ -142,9 +157,9 @@ public class LoanScheduleParams { return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, - compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, - recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency, - applyInterestRecalculation); + compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, + outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, + scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); } public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection recalculationDetails, @@ -162,7 +177,7 @@ public class LoanScheduleParams { final Money reducePrincipal = null; final Map principalPortionMap = null; final Map latePaymentMap = null; - final TreeMap compoundingMap = null; + final Map compoundingMap = null; final Map disburseDetailMap = null; final Money principalToBeScheduled = null; final Money outstandingBalance = null; @@ -172,12 +187,13 @@ public class LoanScheduleParams { final int loanTermInDays = 0; final Money totalOutstandingInterestPaymentDueToGrace = null; final MonetaryCurrency currency = null; + final Money unCompoundedAmount = null; return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, - compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, - recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency, - applyInterestRecalculation); + compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, + outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, + scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); } public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement, @@ -195,7 +211,7 @@ public class LoanScheduleParams { final Money reducePrincipal = Money.zero(currency); final Map principalPortionMap = new HashMap<>(); final Map latePaymentMap = new HashMap<>(); - final TreeMap compoundingMap = new TreeMap<>(); + final Map compoundingMap = new TreeMap<>(); final Map disburseDetailMap = new TreeMap<>(); final Money outstandingBalance = principalToBeScheduled; final Money outstandingBalanceAsPerRest = principalToBeScheduled; @@ -205,12 +221,13 @@ public class LoanScheduleParams { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null; final LocalDate scheduleTillDate = null; final boolean applyInterestRecalculation = false; + final Money unCompoundedAmount = Money.zero(currency); return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, - compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, - recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency, - applyInterestRecalculation); + compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, + outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, + scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); } public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement, @@ -228,7 +245,7 @@ public class LoanScheduleParams { final Money reducePrincipal = Money.zero(currency); final Map principalPortionMap = new HashMap<>(); final Map latePaymentMap = new HashMap<>(); - final TreeMap compoundingMap = new TreeMap<>(); + final Map compoundingMap = new TreeMap<>(); final Map disburseDetailMap = new TreeMap<>(); final Money outstandingBalance = principalToBeScheduled; final Money outstandingBalanceAsPerRest = principalToBeScheduled; @@ -238,12 +255,13 @@ public class LoanScheduleParams { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor; final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate; final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation; + final Money unCompoundedAmount = Money.zero(currency); return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, - compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, - recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency, - applyInterestRecalculation); + compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, + outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, + scheduleTillDate, partialUpdate, currency, applyInterestRecalculation); } public int getPeriodNumber() { @@ -310,7 +328,7 @@ public class LoanScheduleParams { return this.latePaymentMap; } - public TreeMap getCompoundingMap() { + public Map getCompoundingMap() { return this.compoundingMap; } @@ -434,7 +452,7 @@ public class LoanScheduleParams { this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace; } - public Map> getCompoundingDateVariations() { + public Map> getCompoundingDateVariations() { return this.compoundingDateVariations; } @@ -446,4 +464,18 @@ public class LoanScheduleParams { return this.applyInterestRecalculation; } + public Money getUnCompoundedAmount() { + return this.unCompoundedAmount; + } + public void addUnCompoundedAmount(Money unCompoundedAmount) { + this.unCompoundedAmount = this.unCompoundedAmount.plus(unCompoundedAmount); + } + + public void minusUnCompoundedAmount(Money unCompoundedAmount) { + this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount); + } + + public void setUnCompoundedAmount(Money unCompoundedAmount) { + this.unCompoundedAmount = unCompoundedAmount; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index 2f8b72824..d06b3f1b0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanSummary; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @@ -124,7 +125,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO); final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate( - loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate); + loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate, + loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms); if (!scheduleParams.isPartialUpdate()) { // Set Fixed Principal Amount @@ -203,13 +205,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (scheduleParams.getPeriodStartDate().isAfter(scheduledDueDate)) { throw new ScheduleDateException( "Due date can't be before period start date", scheduledDueDate); } - if (!scheduleParams.getLatePaymentMap().isEmpty()) { - populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, currentDate, loanApplicationTerms, - holidayDetailDTO, scheduleParams.getCompoundingMap(), loanCharges, currency); - scheduleParams.getCompoundingDateVariations().put(scheduleParams.getPeriodStartDate(), - new TreeMap<>(scheduleParams.getCompoundingMap())); - } - if (extendTermForDailyRepayments) { scheduleParams.setActualRepaymentDate(scheduledDueDate); } @@ -220,6 +215,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduledDueDate = scheduleParams.getScheduleTillDate(); isNextRepaymentAvailable = false; } + if (loanApplicationTerms.isInterestRecalculationEnabled()) { + populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, + holidayDetailDTO, scheduleParams, loanCharges, currency); + } // populates the collection with transactions till the due date of // the period for interest recalculation enabled loans @@ -316,6 +315,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(), totalInstallmentDue, false); + addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); // apply loan transactions on installments to identify early/late // payments for interest recalculation installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, @@ -327,7 +327,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // Updates principal paid map with efective date for reducing // the amount from outstanding balance(interest calculation) updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams, - installment); + installment, lastRestDate); // handle cumulative fields @@ -337,7 +337,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduleParams.setPeriodStartDate(scheduledDueDate); scheduleParams.incrementInstalmentNumber(); scheduleParams.incrementPeriodNumber(); - scheduleParams.getCompoundingDateVariations().clear(); if (termVariationParams.isRecalculateAmounts()) { loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null); loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null); @@ -377,6 +376,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final BigDecimal totalPrincipalPaid = BigDecimal.ZERO; final BigDecimal totalOutstanding = BigDecimal.ZERO; + updateCompoundingDetails(periods, scheduleParams, loanApplicationTerms); return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(), scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(), @@ -384,6 +384,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalOutstanding); } + private void updateCompoundingDetails(final Collection periods, final LoanScheduleParams params, + final LoanApplicationTerms loanApplicationTerms) { + final Map> compoundingDetails = params.getCompoundingDateVariations(); + if (compoundingDetails.isEmpty()) { return; } + for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { + if (loanScheduleModelPeriod.isRepaymentPeriod() && loanScheduleModelPeriod.getLoanCompoundingDetails().isEmpty()) { + Map periodCompoundingDetails = compoundingDetails.get(loanScheduleModelPeriod.periodFromDate()); + if (periodCompoundingDetails != null) { + for (Map.Entry entry : periodCompoundingDetails.entrySet()) { + if (entry.getValue().isGreaterThanZero() && !entry.getKey().isAfter(loanScheduleModelPeriod.periodDueDate())) { + LocalDate effectiveDate = entry.getKey(); + if (loanApplicationTerms.allowCompoundingOnEod()) { + effectiveDate = effectiveDate.minusDays(1); + } + LoanInterestRecalcualtionAdditionalDetails additionalDetails = new LoanInterestRecalcualtionAdditionalDetails( + effectiveDate, entry.getValue().getAmount()); + loanScheduleModelPeriod.getLoanCompoundingDetails().add(additionalDetails); + } + } + } + } + } + } + private void applyChargesForCurrentPeriod(final Set loanCharges, final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams) { PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(), @@ -422,13 +446,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams, - LoanScheduleModelPeriod installment) { + LoanScheduleModelPeriod installment, LocalDate lastRestDate) { LocalDate amountApplicableDate = installment.periodDueDate(); if (loanApplicationTerms.isInterestRecalculationEnabled()) { amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); } updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate); + updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate); // update outstanding balance for interest calculation updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate); @@ -445,7 +470,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LoanScheduleModelPeriod modifiedInstallment = installment; if (scheduleParams.applyInterestRecalculation() && loanRepaymentScheduleTransactionProcessor != null) { Money principalProcessed = Money.zero(currency); - addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), modifiedInstallment); for (RecalculationDetail detail : applicableTransactions) { if (!detail.isProcessed()) { LocalDate transactionDate = detail.getTransactionDate(); @@ -489,10 +513,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } } + adjustCompoundedAmountWithPaidDetail(scheduleParams, lastRestDate, currentTransactions, loanApplicationTerms, + holidayDetailDTO); } } updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(), scheduledDueDate, - scheduleParams.getInstallments(), true, lastRestDate, scheduleParams.getCompoundingMap()); + scheduleParams.getInstallments(), true, lastRestDate); currentPeriodParams.minusPrincipalForThisPeriod(principalProcessed); } return modifiedInstallment; @@ -600,9 +626,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduleParams.getCompoundingMap().clear(); scheduleParams.getCompoundingMap().putAll( scheduleParams.getCompoundingDateVariations().get(periodStartDateApplicableForInterest)); - } else { - scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, - new TreeMap<>(scheduleParams.getCompoundingMap())); } } @@ -656,10 +679,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (daysInPeriodApplicable > 0) { // 5 determine interest till the transaction // date - if (!scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) { - scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, - new TreeMap<>(scheduleParams.getCompoundingMap())); - } PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams @@ -700,6 +719,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduleParams.getOutstandingBalance(), interestForThisinstallment, feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue, true); periods.add(installment); + addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); + updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate); // update outstanding balance for interest // calculation as per the rest @@ -716,10 +737,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periodStartDateApplicableForInterest = scheduleParams.getPeriodStartDate(); updateLatePaymentMap = true; scheduleParams.incrementInstalmentNumber(); + populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, + holidayDetailDTO, scheduleParams, loanCharges, currency); // creates and insert Loan repayment schedule // for // the period - addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); + } else if (installment == null) { installment = ((List) periods).get(periods.size() - 1); } @@ -762,12 +785,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // identify late payments and add compounding // details to // map for interest calculation - handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, - periodStartDateApplicableForInterest, detail); + handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail); if (updateLatePaymentMap) { updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(), - scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate, - scheduleParams.getCompoundingMap()); + scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate); } } else if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null) { LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms, @@ -776,9 +797,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener List currentTransactions = createCurrentTransactionList(detail); Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor().handleRepaymentSchedule( currentTransactions, currency, scheduleParams.getInstallments()); - Money arrears = fetchCompoundedArrears(loanApplicationTerms, currency, detail.getTransaction()); + Money arrears = fetchArrears(loanApplicationTerms, currency, detail.getTransaction()); if (unprocessed.isGreaterThanZero()) { - arrears = getTotalAmount(scheduleParams.getLatePaymentMap(), currency); updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed, applicableDate); currentPeriodParams.plusEarlyPaidAmount(unprocessed); @@ -790,10 +810,6 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .calculateTillRestFrequencyEnabled()) { LocalDate calculateTill = transactionDate; - if (!scheduleParams.getCompoundingDateVariations().containsKey(periodStartDateApplicableForInterest)) { - scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, - new TreeMap<>(scheduleParams.getCompoundingMap())); - } PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams @@ -836,8 +852,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } if (arrears.isGreaterThanZero() && applicableDate.isBefore(lastRestDate)) { - handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, - periodStartDateApplicableForInterest, detail); + handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail); } } @@ -862,12 +877,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * @param detail */ private void handleLatePayments(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate lastRestDate, - LocalDate periodStartDateApplicableForInterest, RecalculationDetail detail) { + final MonetaryCurrency currency, LoanScheduleParams scheduleParams, LocalDate lastRestDate, RecalculationDetail detail) { updateLatePaidAmountsToPrincipalMap(detail.getTransaction(), loanApplicationTerms, currency, holidayDetailDTO, lastRestDate, scheduleParams); - scheduleParams.getCompoundingDateVariations().put(periodStartDateApplicableForInterest, - new TreeMap<>(scheduleParams.getCompoundingMap())); } private void updateAmountsBasedOnEarlyPayment(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, @@ -1064,9 +1076,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return isAmountChanged; } - private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, + private Money fetchArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, final LoanTransaction transaction) { Money arrears = transaction.getPrincipalPortion(currency); + arrears = arrears.plus(fetchCompoundedArrears(loanApplicationTerms, currency, transaction)); + return arrears; + } + + private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, + final LoanTransaction transaction) { + Money arrears = Money.zero(currency); if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { arrears = arrears.plus(transaction.getInterestPortion(currency)); } @@ -1096,7 +1115,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Collection transactions, final Set loanCharges, final LoanScheduleParams params) { boolean isFirstRepayment = false; LocalDate startDate = params.getPeriodStartDate(); - Money outstanding = Money.zero(currency); + Money outstanding = params.getOutstandingBalanceAsPerRest(); Money totalInterest = Money.zero(currency); Money totalCumulativeInterest = Money.zero(currency); double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); @@ -1111,15 +1130,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (params.getActualRepaymentDate().isAfter(currentDate)) { params.setActualRepaymentDate(currentDate); } - outstanding = updateOutstandingFromLatePayment(params.getPeriodStartDate(), params.getLatePaymentMap(), outstanding); Collection applicableTransactions = getApplicableTransactionsForPeriod( params.applyInterestRecalculation(), params.getActualRepaymentDate(), transactions); - if (!params.getLatePaymentMap().isEmpty()) { - populateCompoundingDatesInPeriod(params.getPeriodStartDate(), params.getActualRepaymentDate(), currentDate, - loanApplicationTerms, holidayDetailDTO, params.getCompoundingMap(), loanCharges, currency); - } + populateCompoundingDatesInPeriod(params.getPeriodStartDate(), params.getActualRepaymentDate(), loanApplicationTerms, + holidayDetailDTO, params, loanCharges, currency); for (RecalculationDetail detail : applicableTransactions) { if (detail.isProcessed()) { @@ -1146,22 +1162,30 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest); totalInterest = totalInterest.zero(); addLoanRepaymentScheduleInstallment(params.getInstallments(), installment); + updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, params, lastRestDate, transactionDate); + populateCompoundingDatesInPeriod(installment.periodDueDate(), params.getActualRepaymentDate(), loanApplicationTerms, + holidayDetailDTO, params, loanCharges, currency); + params.setCompoundedInLastInstallment(params.getUnCompoundedAmount()); params.setPeriodStartDate(transactionDate); startDate = transactionDate; } loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency, params.getInstallments()); + updateLatePaidAmountsToPrincipalMap(detail.getTransaction(), loanApplicationTerms, currency, holidayDetailDTO, + lastRestDate, params); updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, params.getLatePaymentMap(), currentDate, - params.getInstallments(), false, lastRestDate, params.getCompoundingMap()); - outstanding = outstanding.zero(); - outstanding = updateOutstandingFromLatePayment(params.getPeriodStartDate(), params.getLatePaymentMap(), outstanding); - outstanding = updateBalanceForInterestCalculation(params.getPrincipalPortionMap(), params.getPeriodStartDate(), - outstanding, false); - if (params.getLatePaymentMap().isEmpty() && !outstanding.isGreaterThanZero()) { + params.getInstallments(), false, lastRestDate); + if (params.getLatePaymentMap().isEmpty() && isCompleted(params.getInstallments())) { + outstanding = outstanding.zero(); + } else { + outstanding = updateBalanceForInterestCalculation(params.getPrincipalPortionMap(), params.getPeriodStartDate(), + outstanding, false); + } + if (params.getLatePaymentMap().isEmpty() && outstanding.isZero()) { break; } } - if (outstanding.isGreaterThanZero()) { + if (!outstanding.isZero()) { PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, @@ -1169,15 +1193,31 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener params.getActualRepaymentDate(), applicableVariations); Money interest = principalInterestForThisPeriod.interest(); totalInterest = totalInterest.plus(interest); - if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { - LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(params.getActualRepaymentDate().minusDays(1), - loanApplicationTerms, holidayDetailDTO); - params.getLatePaymentMap().put(compoundingEffectiveDate, interest); + Money uncompounded = params.getUnCompoundedAmount(); + Money compounded = uncompounded.zero(); + for (Map.Entry mapEntry : params.getCompoundingMap().entrySet()) { + if (mapEntry.getKey().isAfter(params.getPeriodStartDate())) { + compounded = compounded.plus(mapEntry.getValue()); + } } + Money compoundedForThisPeriod = compounded.minus(uncompounded); + Money uncompoundedForThisPeriod = interest.minus(compoundedForThisPeriod); + params.setUnCompoundedAmount(uncompoundedForThisPeriod); + LocalDate compoundingDate = params.getPeriodStartDate(); + if (loanApplicationTerms.allowCompoundingOnEod()) { + compoundingDate = compoundingDate.minusDays(1); + } + compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO); + if(compoundingDate.isEqual(params.getActualRepaymentDate())){ + params.getCompoundingMap().put(compoundingDate, uncompoundedForThisPeriod); + params.setUnCompoundedAmount(uncompoundedForThisPeriod.zero()); + } + + } params.setPeriodStartDate(params.getActualRepaymentDate()); - } while (params.getActualRepaymentDate().isBefore(currentDate) && outstanding.isGreaterThanZero()); + } while (params.getActualRepaymentDate().isBefore(currentDate) && !outstanding.isZero()); if (totalInterest.isGreaterThanZero()) { LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(params.getInstalmentNumber(), @@ -1185,11 +1225,23 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalInterest.zero(), totalInterest.zero(), totalInterest, true); params.incrementInstalmentNumber(); periods.add(installment); + params.getCompoundingDateVariations().put(startDate, new TreeMap<>(params.getCompoundingMap())); totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest); } return totalCumulativeInterest; } + private boolean isCompleted(List installments) { + boolean isCompleted = true; + for (LoanRepaymentScheduleInstallment installment : installments) { + if (installment.isNotFullyPaidOff()) { + isCompleted = false; + break; + } + } + return isCompleted; + } + private Collection getApplicableTransactionsForPeriod(final boolean applyInterestRecalculation, LocalDate repaymentDate, final Collection transactions) { Collection applicableTransactions = new ArrayList<>(); @@ -1221,21 +1273,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener currentTransactions.add(detail.getTransaction()); detail.setProcessed(true); return currentTransactions; - } - 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()); - } else { - retainEntries.put(mapEntry.getKey(), mapEntry.getValue()); - } - } - latePaymentMap.clear(); - latePaymentMap.putAll(retainEntries); - retainEntries.clear(); - return outstanding; } /** @@ -1315,22 +1353,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener 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(params.getPrincipalPortionMap(), params.getLatePaymentMap(), currency, lastRestDate, principalPortion, - applicableDate); - updateCompoundingAmount(params.getPrincipalPortionMap(), params.getCompoundingMap(), currency, lastRestDate, - compoundedLatePayments, applicableDate); + updateLatePaymentCompoundingAmount(params.getPrincipalPortionMap(), params.getLatePaymentMap(), currency, lastRestDate, + principalPortion, applicableDate); + adjustCompoundedAmountWithPaidDetail(params, lastRestDate, applicableDate, loanTransaction, applicationTerms); } - private void updateCompoundingAmount(final Map principalVariationMap, + private void updateLatePaymentCompoundingAmount(final Map principalVariationMap, final Map latePaymentCompoundingMap, final MonetaryCurrency currency, final LocalDate lastRestDate, Money compoundedPortion, final LocalDate applicableDate) { Money appliedOnPrincipalVariationMap = Money.zero(currency); @@ -1368,17 +1397,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener */ private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final Map latePaymentMap, final LocalDate scheduledDueDate, - List installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate, - final TreeMap compoundingMap) { + List installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate) { 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) @@ -1395,106 +1418,183 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener .plus(loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency)); } - 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(), lastRestDate); } - if (clearCompoundingMap) { - compoundingMap.clear(); - } } - private Money updateMapWithCompoundingDetails(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - 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()) { - case INTEREST: - amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency)); - break; - case FEE: - amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)); - amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); - break; - case INTEREST_AND_FEE: - amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency)); - amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)); - amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); - break; - default: - break; - } - if (compoundingEffectiveDate.isBefore(scheduledDueDate)) { - ignoreMoney = ignoreMoney.plus(amount); - if (ignoreMoney.isGreaterThanZero()) { - updateMapWithAmount(compoundingMap, ignoreMoney, compoundingEffectiveDate); - updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate); - ignoreMoney = ignoreMoney.zero(); - } + private void updateCompoundingMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, + final LoanScheduleParams params, final LocalDate lastRestDate, final LocalDate scheduledDueDate) { + if (loanApplicationTerms.isInterestRecalculationEnabled() + && loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { + final MonetaryCurrency currency = params.getCurrency(); + Money totalCompoundedAmount = Money.zero(currency); + boolean lastInstallmentIsPastDate = false; + for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : params.getInstallments()) { + if (params.getCompoundingDateVariations().containsKey(loanRepaymentScheduleInstallment.getFromDate())) { + lastInstallmentIsPastDate = params.applyInterestRecalculation() + && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant()); } else { - if (ignoreMoney.isLessThanZero()) { - LocalDate firstKey = compoundingMap.firstKey(); - updateMapWithAmount(compoundingMap, ignoreMoney, firstKey); - updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate); - ignoreMoney = ignoreMoney.zero(); + final boolean isPastDate = params.applyInterestRecalculation() + && loanRepaymentScheduleInstallment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant()); + boolean periodHasCompoundingDate = false; + Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, loanRepaymentScheduleInstallment); + final Map compoundingMap = params.getCompoundingMap(); + LocalDate effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate(); + if (loanApplicationTerms.allowCompoundingOnEod()) { + effectiveStartDate = loanRepaymentScheduleInstallment.getFromDate().minusDays(1); } - updateMapWithAmount(compoundingMap, amount, compoundingEffectiveDate); - updateMapWithAmount(compoundingMap, amount.negated(), lastRestDate); + LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(effectiveStartDate, loanApplicationTerms, + holidayDetailDTO); + final LocalDate restDate = getNextRestScheduleDate(scheduledDueDate.minusDays(1), loanApplicationTerms, + holidayDetailDTO); + if (!compoundingEffectiveDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())) { + totalCompoundedAmount = totalCompoundedAmount.minus(params.getUnCompoundedAmount()); + periodHasCompoundingDate = true; + } + while (!compoundingEffectiveDate.isAfter(loanRepaymentScheduleInstallment.getDueDate())) { + if (compoundingEffectiveDate.isEqual(loanRepaymentScheduleInstallment.getDueDate())) { + Money amountToBeCompounding = amountCharged.minus(totalCompoundedAmount); + updateMapWithAmount(compoundingMap, amountToBeCompounding, compoundingEffectiveDate); + totalCompoundedAmount = totalCompoundedAmount.plus(amountToBeCompounding); + + } else if (compoundingMap.containsKey(compoundingEffectiveDate)) { + Money compounedAmount = compoundingMap.get(compoundingEffectiveDate); + totalCompoundedAmount = totalCompoundedAmount.plus(compounedAmount); + } + + if (!loanApplicationTerms.allowCompoundingOnEod()) { + compoundingEffectiveDate = compoundingEffectiveDate.plusDays(1); + } + compoundingEffectiveDate = getNextCompoundScheduleDate(compoundingEffectiveDate, loanApplicationTerms, + holidayDetailDTO); + } + if (periodHasCompoundingDate) { + if (isPastDate) { + updateMapWithAmount(params.getPrincipalPortionMap(), + totalCompoundedAmount.plus(params.getUnCompoundedAmount()), lastRestDate); + } else { + Money amountToBeEffected = amountCharged; + if (lastInstallmentIsPastDate) { + amountToBeEffected = amountToBeEffected.plus(params.getUnCompoundedAmount()); + } + updateMapWithAmount(params.getPrincipalPortionMap(), amountToBeEffected, restDate); + } + } + if (totalCompoundedAmount.isGreaterThanZero()) { + params.getCompoundingDateVariations().put(loanRepaymentScheduleInstallment.getFromDate(), + new TreeMap<>(params.getCompoundingMap())); + for (Map.Entry mapEntry : params.getCompoundingMap().entrySet()) { + if (!mapEntry.getKey().isAfter(loanRepaymentScheduleInstallment.getDueDate())) { + updateMapWithAmount(params.getPrincipalPortionMap(), mapEntry.getValue().negated(), mapEntry.getKey()); + }else if(params.getUnCompoundedAmount().isEqualTo( mapEntry.getValue())){ + totalCompoundedAmount = totalCompoundedAmount.plus(params.getUnCompoundedAmount()); + } + } + params.minusUnCompoundedAmount(params.getUnCompoundedAmount()); + params.getCompoundingMap().clear(); + params.addUnCompoundedAmount(amountCharged.minus(totalCompoundedAmount.minus(params.getCompoundedInLastInstallment()))); + } else { + params.getCompoundingMap().clear(); + params.getCompoundingDateVariations().put(loanRepaymentScheduleInstallment.getFromDate(), + new TreeMap<>(params.getCompoundingMap())); + params.addUnCompoundedAmount(amountCharged); + } + params.setCompoundedInLastInstallment(amountCharged.zero()); + lastInstallmentIsPastDate = isPastDate; } + } } - return ignoreMoney; + } - private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LocalDate currentDate, + private Money getIncomeForCompounding(final LoanApplicationTerms loanApplicationTerms, final MonetaryCurrency currency, + LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) { + Money interestCharged = Money.zero(currency); + Money feeCharged = Money.zero(currency); + Money penaltyCharged = Money.zero(currency); + Money amountCharged = Money.zero(currency); + switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) { + case INTEREST: + interestCharged = interestCharged.plus(loanRepaymentScheduleInstallment.getInterestCharged(currency)); + break; + case FEE: + feeCharged = feeCharged.plus(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency)); + penaltyCharged = penaltyCharged.plus(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency)); + break; + case INTEREST_AND_FEE: + interestCharged = interestCharged.plus(loanRepaymentScheduleInstallment.getInterestCharged(currency)); + feeCharged = feeCharged.plus(loanRepaymentScheduleInstallment.getFeeChargesCharged(currency)); + penaltyCharged = penaltyCharged.plus(loanRepaymentScheduleInstallment.getPenaltyChargesCharged(currency)); + break; + default: + break; + } + amountCharged = interestCharged.plus(feeCharged).plus(penaltyCharged); + return amountCharged; + } + + private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate, + final Collection transactions, final LoanApplicationTerms loanApplicationTerms, + HolidayDetailDTO holidayDetailDTO) { + for (LoanTransaction loanTransaction : transactions) { + final LocalDate amountApplicableDate = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1), + loanApplicationTerms, holidayDetailDTO); + adjustCompoundedAmountWithPaidDetail(params, lastRestDate, amountApplicableDate, loanTransaction, loanApplicationTerms); + } + } + + private void adjustCompoundedAmountWithPaidDetail(final LoanScheduleParams params, final LocalDate lastRestDate, + final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms) { + adjustCompoundedAmountWithPaidDetail(params.getPrincipalPortionMap(), lastRestDate, amountApplicableDate, transaction, + loanApplicationTerms, params.getCurrency()); + } + + private void adjustCompoundedAmountWithPaidDetail(final Map principalPortionMap, final LocalDate lastRestDate, + final LocalDate amountApplicableDate, final LoanTransaction transaction, final LoanApplicationTerms loanApplicationTerms, + final MonetaryCurrency currency) { + if (!amountApplicableDate.isEqual(lastRestDate)) { + Money compoundedIncome = fetchCompoundedArrears(loanApplicationTerms, currency, transaction); + updateMapWithAmount(principalPortionMap, compoundedIncome, amountApplicableDate); + updateMapWithAmount(principalPortionMap, compoundedIncome.negated(), lastRestDate); + } + } + + private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, - final Map compoundingMap, final Set charges, MonetaryCurrency currency) { + final LoanScheduleParams scheduleParams, final Set charges, MonetaryCurrency currency) { if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { + final Map compoundingMap = scheduleParams.getCompoundingMap(); LocalDate lastCompoundingDate = startDate; LocalDate compoundingDate = startDate; - while (compoundingDate.isBefore(endDate) && compoundingDate.isBefore(currentDate)) { + boolean addUncompounded = true; + while (compoundingDate.isBefore(endDate)) { + if (loanApplicationTerms.allowCompoundingOnEod()) { + compoundingDate = compoundingDate.minusDays(1); + } compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO); - if (!compoundingDate.isBefore(currentDate)) { - break; - } else if (compoundingDate.isAfter(endDate)) { - updateMapWithAmount(compoundingMap, Money.zero(currency), compoundingDate); - } else { + + if (compoundingDate.isBefore(endDate)) { Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null, false); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null, false); - updateMapWithAmount(compoundingMap, feeChargesForInstallment.plus(penaltyChargesForInstallment), compoundingDate); + Money compoundAmount = feeChargesForInstallment.plus(penaltyChargesForInstallment); + if (addUncompounded) { + compoundAmount = compoundAmount.plus(scheduleParams.getUnCompoundedAmount()); + addUncompounded = false; + } + updateMapWithAmount(compoundingMap, compoundAmount, 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); - } - /** * This Method updates principal paid component to map with effective date * as per the REST @@ -1580,16 +1680,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener principalPaid = map.get(amountApplicableDate).plus(principalPaid); } map.put(amountApplicableDate, principalPaid); - } - private Money getTotalAmount(final Map map, final MonetaryCurrency currency) { - Money total = Money.zero(currency); - for (Map.Entry mapEntry : map.entrySet()) { - if (mapEntry.getKey().isBefore(DateUtils.getLocalDateOfTenant())) { - total = total.plus(mapEntry.getValue()); - } - } - return total; } @Override @@ -1737,8 +1828,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } // get the loan application terms from the Loan object - final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance, - compoundingCalendarInstance, loanCalendar, floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays); + final LoanApplicationTerms loanApplicationTerms = loan + .getLoanApplicationTerms(applicationCurrency, restCalendarInstance, compoundingCalendarInstance, loanCalendar, + floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays, holidayDetailDTO); // for applying variations Collection loanTermVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); @@ -2117,6 +2209,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // action will be performed on this value Money reducePrincipal = outstandingBalanceAsPerRest.zero(); + Money uncompoundedAmount = outstandingBalanceAsPerRest.zero(); // principal changes will be added along with date(after applying // rest) // from when these amounts will effect the outstanding balance for @@ -2133,6 +2226,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // from when these amounts will effect the outstanding balance for // interest calculation final TreeMap compoundingMap = new TreeMap<>(); + final Map> compoundingDateVariations = new HashMap<>(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); LocalDate lastRestDate = currentDate; if (loanApplicationTerms.getRestCalendarInstance() != null) { @@ -2179,6 +2273,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // process the installment only if recalculate from date is // greater than due date if (installment.getDueDate().isAfter(lastInstallmentDate)) { + if (totalCumulativePrincipal.isGreaterThanOrEqualTo(loanApplicationTerms.getTotalDisbursedAmount())) { + break; + } LocalDate previousRepaymentDate = actualRepaymentDate; actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); @@ -2272,6 +2369,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // updates map with the installment principal amount excluding // unprocessed amount since this amount is already accounted. updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate); + uncompoundedAmount = updateCompoundingDetailsForPartialScheduleGeneration(installment, loanApplicationTerms, + principalPortionMap, compoundingDateVariations, uncompoundedAmount, applicableTransactions, lastRestDate, + holidayDetailDTO); // update outstanding balance for interest calculation outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), outstandingBalanceAsPerRest, false); @@ -2284,7 +2384,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // updates the map with over due amounts updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, - newRepaymentScheduleInstallments, true, lastRestDate, compoundingMap); + newRepaymentScheduleInstallments, true, lastRestDate); // for partial schedule generation if (!newRepaymentScheduleInstallments.isEmpty() && totalCumulativeInterest.isGreaterThanZero()) { @@ -2293,10 +2393,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, - disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, + uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, currency, applyInterestRecalculation); retainedInstallments.addAll(newRepaymentScheduleInstallments); + loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); } } @@ -2328,6 +2429,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener final Map principalPortionMap, LoanRepaymentScheduleInstallment installment, Collection applicableTransactions, Money actualPrincipalPortion) { Money unprocessed = Money.zero(currency); + Money totalUnprocessed = Money.zero(currency); for (RecalculationDetail detail : applicableTransactions) { if (!detail.isProcessed()) { Money principalProcessed = installment.getPrincipalCompleted(currency); @@ -2357,10 +2459,53 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); updateMapWithAmount(principalPortionMap, unprocessed, applicableDate); + totalUnprocessed = totalUnprocessed.plus(unprocessed); } } - return unprocessed; + return totalUnprocessed; + } + + private Money updateCompoundingDetailsForPartialScheduleGeneration(final LoanRepaymentScheduleInstallment installment, + LoanApplicationTerms loanApplicationTerms, Map principalMap, + final Map> compoundingDateVariations, final Money uncompoundedAmount, + final Collection applicableTransactions, LocalDate lastRestDate, HolidayDetailDTO holidayDetailDTO) { + Money uncompounded = uncompoundedAmount; + MonetaryCurrency currency = uncompoundedAmount.getCurrency(); + for (RecalculationDetail detail : applicableTransactions) { + LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + adjustCompoundedAmountWithPaidDetail(principalMap, lastRestDate, applicableDate, detail.getTransaction(), loanApplicationTerms, + currency); + } + Money amountCharged = getIncomeForCompounding(loanApplicationTerms, currency, installment); + final List details = installment.getLoanCompoundingDetails(); + Money totalCompounded = Money.zero(currency); + Map compoundingMap = new TreeMap<>(); + for (LoanInterestRecalcualtionAdditionalDetails additionalDetails : details) { + LocalDate effectiveDate = additionalDetails.getEffectiveDate(); + if (loanApplicationTerms.allowCompoundingOnEod()) { + effectiveDate = effectiveDate.plusDays(1); + } + compoundingMap.put(effectiveDate, Money.of(currency, additionalDetails.getAmount())); + totalCompounded = totalCompounded.plus(additionalDetails.getAmount()); + updateMapWithAmount(principalMap, Money.of(currency, additionalDetails.getAmount()).negated(), effectiveDate); + } + compoundingDateVariations.put(installment.getFromDate(), compoundingMap); + if (totalCompounded.isGreaterThanZero()) { + final boolean isPastDate = installment.getDueDate().isBefore(DateUtils.getLocalDateOfTenant()); + final LocalDate restDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, + holidayDetailDTO); + if (isPastDate) { + updateMapWithAmount(principalMap, totalCompounded, lastRestDate); + } else { + updateMapWithAmount(principalMap, totalCompounded, restDate); + } + uncompounded = amountCharged.plus(uncompounded).minus(totalCompounded); + } else { + uncompounded = uncompounded.plus(amountCharged); + } + return uncompounded; } private void updateAmortization(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, int periodNumber, @@ -2432,7 +2577,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(), - scheduledLoanInstallment.isRecalculatedInterestComponent()); + scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails()); installments.add(installment); } return installment; @@ -2473,6 +2618,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } else { CalendarInstance calendarInstance = loanApplicationTerms.getCompoundingCalendarInstance(); nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate); + if (loanApplicationTerms.allowCompoundingOnEod()) { + nextScheduleDate = nextScheduleDate.plusDays(1); + } } return nextScheduleDate; @@ -2510,9 +2658,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency)); } } - + final List compoundingDetails = null; return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(), - feeCharges.getAmount(), penaltyCharges.getAmount(), false); + feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails); } /** diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java index f53a50ef5..1bbefa4bd 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java @@ -25,7 +25,6 @@ import java.util.HashMap; import java.util.Map; import java.util.TreeMap; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; @@ -70,7 +69,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS LocalDate interestStartDate = periodStartDate; Money interestForThisInstallment = totalCumulativePrincipal.zero(); - Money compoundedMoney = totalCumulativePrincipal.zero(); Money compoundedInterest = totalCumulativePrincipal.zero(); Money balanceForInterestCalculation = outstandingBalance; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; @@ -89,13 +87,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } } 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()) { @@ -120,7 +111,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } // fee compounding will be done after calculation compoundFee = compoundingMap.get(principal.getKey()); - compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee); + compoundingMap.put(principal.getKey(), interestToBeCompounded.plus(compoundFee)); } balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()).plus(compoundFee); if (interestRates.containsKey(principal.getKey())) { @@ -129,14 +120,6 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } } - 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); - } } final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index 7afc00289..b57f215e1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -62,8 +62,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { } else { Calendar currentCalendar = loanApplicationTerms.getLoanCalendar(); dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), - loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(), - loanApplicationTerms.getWeekDayType()); + loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, null, + null); dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(), loanApplicationTerms.getRepaymentPeriodFrequencyType()); if (currentCalendar != null) { @@ -219,7 +219,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType, - final int repaidEvery, final LocalDate firstRepaymentDate) { + final int repaidEvery, final LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO, + final LoanApplicationTerms loanApplicationTerms) { LocalDate idealDisbursementDate = null; @@ -231,7 +232,15 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { idealDisbursementDate = firstRepaymentDate.minusWeeks(repaidEvery); break; case MONTHS: - idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery); + if (loanCalendar == null) { + idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery); + } else { + idealDisbursementDate = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(), + firstRepaymentDate.minusMonths(repaidEvery), firstRepaymentDate.minusMonths(repaidEvery), repaidEvery, + CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType), + holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(), + loanApplicationTerms.getNumberOfdays()); + } break; case YEARS: idealDisbursementDate = firstRepaymentDate.minusYears(repaidEvery); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java old mode 100755 new mode 100644 index 7be11d321..c1f91eaf0 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -30,12 +30,14 @@ import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.common.domain.DayOfWeekType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInYearType; import org.apache.fineract.portfolio.common.domain.NthDayType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.loanaccount.data.DisbursementData; +import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsDataWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails; @@ -57,1864 +59,1521 @@ import org.joda.time.Years; public final class LoanApplicationTerms { - private final ApplicationCurrency currency; - - private final Calendar loanCalendar; - private Integer loanTermFrequency; - private final PeriodFrequencyType loanTermPeriodFrequencyType; - private Integer numberOfRepayments; - private Integer actualNumberOfRepayments; - private final Integer repaymentEvery; - private final PeriodFrequencyType repaymentPeriodFrequencyType; - private final Integer nthDay; + private final ApplicationCurrency currency; + + private final Calendar loanCalendar; + private Integer loanTermFrequency; + private final PeriodFrequencyType loanTermPeriodFrequencyType; + private Integer numberOfRepayments; + private Integer actualNumberOfRepayments; + private final Integer repaymentEvery; + private final PeriodFrequencyType repaymentPeriodFrequencyType; + private final Integer nthDay; - private final DayOfWeekType weekDayType; - private final AmortizationMethod amortizationMethod; + private final DayOfWeekType weekDayType; + private final AmortizationMethod amortizationMethod; - private final InterestMethod interestMethod; - private BigDecimal interestRatePerPeriod; - private final PeriodFrequencyType interestRatePeriodFrequencyType; - private BigDecimal annualNominalInterestRate; - private final InterestCalculationPeriodMethod interestCalculationPeriodMethod; - private final boolean allowPartialPeriodInterestCalcualtion; + private final InterestMethod interestMethod; + private BigDecimal interestRatePerPeriod; + private final PeriodFrequencyType interestRatePeriodFrequencyType; + private BigDecimal annualNominalInterestRate; + private final InterestCalculationPeriodMethod interestCalculationPeriodMethod; + private final boolean allowPartialPeriodInterestCalcualtion; - private Money principal; - private final LocalDate expectedDisbursementDate; - private final LocalDate repaymentsStartingFromDate; - private final LocalDate calculatedRepaymentsStartingFromDate; - /** - * Integer representing the number of 'repayment frequencies' or - * installments where 'grace' should apply to the principal component of a - * loans repayment period (installment). - */ - private Integer principalGrace; - private Integer recurringMoratoriumOnPrincipalPeriods; + private Money principal; + private final LocalDate expectedDisbursementDate; + private final LocalDate repaymentsStartingFromDate; + private final LocalDate calculatedRepaymentsStartingFromDate; + /** + * Integer representing the number of 'repayment frequencies' or + * installments where 'grace' should apply to the principal component of a + * loans repayment period (installment). + */ + private Integer principalGrace; + private Integer recurringMoratoriumOnPrincipalPeriods; - /** - * Integer representing the number of 'repayment frequencies' or - * installments where 'grace' should apply to the payment of interest in a - * loans repayment period (installment). - * - * Note: Interest is still calculated taking into account the full - * loan term, the interest is simply offset to a later period. - */ - private Integer interestPaymentGrace; + /** + * Integer representing the number of 'repayment frequencies' or + * installments where 'grace' should apply to the payment of interest in a + * loans repayment period (installment). + * + * Note: Interest is still calculated taking into account the full + * loan term, the interest is simply offset to a later period. + */ + private Integer interestPaymentGrace; - /** - * Integer representing the number of 'repayment frequencies' or - * installments where 'grace' should apply to the charging of interest in a - * loans repayment period (installment). - * - * Note: The loan is interest-free for the period of time - * indicated. - */ - private final Integer interestChargingGrace; - - /** - * Legacy method of support 'grace' on the charging of interest on a loan. - * - *

- * For the typical structured loan, its reasonable to use an integer to - * indicate the number of 'repayment frequency' periods the 'grace' should - * apply to but for slightly irregular loans where the period between - * disbursement and the date of the 'first repayment period' isnt doest - * match the 'repayment frequency' but can be less (15days instead of 1 - * month) or more (6 weeks instead of 1 month) - The idea was to use a date - * to indicate from whence interest should be charged. - *

- */ - private LocalDate interestChargedFromDate; - private final Money inArrearsTolerance; - - private final Integer graceOnArrearsAgeing; - - // added - private LocalDate loanEndDate; - - private final List disbursementDatas; - - private final boolean multiDisburseLoan; - - private BigDecimal fixedEmiAmount; - - private BigDecimal fixedPrincipalAmount; - - private BigDecimal currentPeriodFixedEmiAmount; - - private BigDecimal currentPeriodFixedPrincipalAmount; - - private final BigDecimal actualFixedEmiAmount; - - private final BigDecimal maxOutstandingBalance; - - private Money totalInterestDue; - - private final DaysInMonthType daysInMonthType; - - private final DaysInYearType daysInYearType; - - private final boolean interestRecalculationEnabled; - - private final LoanRescheduleStrategyMethod rescheduleStrategyMethod; - - private final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod; - - private final CalendarInstance restCalendarInstance; - - private final RecalculationFrequencyType recalculationFrequencyType; - - private final CalendarInstance compoundingCalendarInstance; - - private final RecalculationFrequencyType compoundingFrequencyType; - - private final BigDecimal principalThresholdForLastInstalment; - private final Integer installmentAmountInMultiplesOf; - - private final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy; - - private Money approvedPrincipal = null; - - private final LoanTermVariationsDataWrapper variationsDataWrapper; - - private Money adjustPrincipalForFlatLoans; - - private final LocalDate seedDate; - - private final CalendarHistoryDataWrapper calendarHistoryDataWrapper; - - private final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled; - - private final Integer numberOfDays; - - private final boolean isSkipRepaymentOnFirstDayOfMonth; - - public static LoanApplicationTerms assembleFrom( - final ApplicationCurrency currency, - final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, - final Integer numberOfRepayments, - final Integer repaymentEvery, - final PeriodFrequencyType repaymentPeriodFrequencyType, - Integer nthDay, - DayOfWeekType weekDayType, - final AmortizationMethod amortizationMethod, - final InterestMethod interestMethod, - final BigDecimal interestRatePerPeriod, - final PeriodFrequencyType interestRatePeriodFrequencyType, - final BigDecimal annualNominalInterestRate, - final InterestCalculationPeriodMethod interestCalculationPeriodMethod, - final boolean allowPartialPeriodInterestCalcualtion, - final Money principalMoney, - final LocalDate expectedDisbursementDate, - final LocalDate repaymentsStartingFromDate, - final LocalDate calculatedRepaymentsStartingFromDate, - final Integer graceOnPrincipalPayment, - final Integer recurringMoratoriumOnPrincipalPeriods, - final Integer graceOnInterestPayment, - final Integer graceOnInterestCharged, - final LocalDate interestChargedFromDate, - final Money inArrearsTolerance, - final boolean multiDisburseLoan, - final BigDecimal emiAmount, - final List disbursementDatas, - final BigDecimal maxOutstandingBalance, - 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, - final Calendar loanCalendar, BigDecimal approvedAmount, - List loanTermVariations, - Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, - final Integer numberOfdays, boolean isSkipRepaymentOnFirstDayofMonth) { - - final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; - final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - return new LoanApplicationTerms(currency, loanTermFrequency, - loanTermPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentPeriodFrequencyType, nthDay, - weekDayType, amortizationMethod, interestMethod, - interestRatePerPeriod, interestRatePeriodFrequencyType, - annualNominalInterestRate, interestCalculationPeriodMethod, - allowPartialPeriodInterestCalcualtion, principalMoney, - expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, - recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, - graceOnInterestCharged, interestChargedFromDate, - inArrearsTolerance, multiDisburseLoan, emiAmount, - disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing, - daysInMonthType, daysInYearType, - isInterestRecalculationEnabled, rescheduleStrategyMethod, - interestRecalculationCompoundingMethod, restCalendarInstance, - recalculationFrequencyType, compoundingCalendarInstance, - compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, - preClosureInterestCalculationStrategy, loanCalendar, - approvedAmount, loanTermVariations, calendarHistoryDataWrapper, - isInterestChargedFromDateSameAsDisbursalDateEnabled, - numberOfdays, isSkipRepaymentOnFirstDayofMonth); - - } - - public static LoanApplicationTerms assembleFrom( - final ApplicationCurrency applicationCurrency, - final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, - NthDayType nthDay, - DayOfWeekType dayOfWeek, - final LocalDate expectedDisbursementDate, - final LocalDate repaymentsStartingFromDate, - final LocalDate calculatedRepaymentsStartingFromDate, - final Money inArrearsTolerance, - final LoanProductRelatedDetail loanProductRelatedDetail, - final boolean multiDisburseLoan, - final BigDecimal emiAmount, - final List disbursementDatas, - final BigDecimal maxOutstandingBalance, - final LocalDate interestChargedFromDate, - final BigDecimal principalThresholdForLastInstalment, - final Integer installmentAmountInMultiplesOf, - final RecalculationFrequencyType recalculationFrequencyType, - final CalendarInstance restCalendarInstance, - final InterestRecalculationCompoundingMethod compoundingMethod, - final CalendarInstance compoundingCalendarInstance, - final RecalculationFrequencyType compoundingFrequencyType, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, - final LoanRescheduleStrategyMethod rescheduleStrategyMethod, - BigDecimal approvedAmount, BigDecimal annualNominalInterestRate, - List loanTermVariations, - final Integer numberOfdays, - final boolean isSkipRepaymentOnFirstDayofMonth) { - final Calendar loanCalendar = null; - final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - - return assembleFrom(applicationCurrency, loanTermFrequency, - loanTermPeriodFrequencyType, nthDay, dayOfWeek, - expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, inArrearsTolerance, - loanProductRelatedDetail, multiDisburseLoan, emiAmount, - disbursementDatas, maxOutstandingBalance, - interestChargedFromDate, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, recalculationFrequencyType, - restCalendarInstance, compoundingMethod, - compoundingCalendarInstance, compoundingFrequencyType, - loanPreClosureInterestCalculationStrategy, - rescheduleStrategyMethod, loanCalendar, approvedAmount, - annualNominalInterestRate, loanTermVariations, - calendarHistoryDataWrapper, numberOfdays, - isSkipRepaymentOnFirstDayofMonth); - } - - public static LoanApplicationTerms assembleFrom( - final ApplicationCurrency applicationCurrency, - final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, - NthDayType nthDay, - DayOfWeekType dayOfWeek, - final LocalDate expectedDisbursementDate, - final LocalDate repaymentsStartingFromDate, - final LocalDate calculatedRepaymentsStartingFromDate, - final Money inArrearsTolerance, - final LoanProductRelatedDetail loanProductRelatedDetail, - final boolean multiDisburseLoan, - final BigDecimal emiAmount, - final List disbursementDatas, - final BigDecimal maxOutstandingBalance, - final LocalDate interestChargedFromDate, - final BigDecimal principalThresholdForLastInstalment, - final Integer installmentAmountInMultiplesOf, - final RecalculationFrequencyType recalculationFrequencyType, - final CalendarInstance restCalendarInstance, - final InterestRecalculationCompoundingMethod compoundingMethod, - final CalendarInstance compoundingCalendarInstance, - final RecalculationFrequencyType compoundingFrequencyType, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, - final LoanRescheduleStrategyMethod rescheduleStrategyMethod, - final Calendar loanCalendar, BigDecimal approvedAmount, - BigDecimal annualNominalInterestRate, - final List loanTermVariations, - final CalendarHistoryDataWrapper calendarHistoryDataWrapper, - final Integer numberOfdays, - final boolean isSkipRepaymentOnFirstDayofMonth) { - - final Integer numberOfRepayments = loanProductRelatedDetail - .getNumberOfRepayments(); - final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); - final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail - .getRepaymentPeriodFrequencyType(); - final AmortizationMethod amortizationMethod = loanProductRelatedDetail - .getAmortizationMethod(); - final InterestMethod interestMethod = loanProductRelatedDetail - .getInterestMethod(); - final BigDecimal interestRatePerPeriod = loanProductRelatedDetail - .getNominalInterestRatePerPeriod(); - final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail - .getInterestPeriodFrequencyType(); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail - .getInterestCalculationPeriodMethod(); - final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail - .isAllowPartialPeriodInterestCalcualtion(); - final Money principalMoney = loanProductRelatedDetail.getPrincipal(); - - // - final Integer graceOnPrincipalPayment = loanProductRelatedDetail - .graceOnPrincipalPayment(); - final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail - .recurringMoratoriumOnPrincipalPeriods(); - final Integer graceOnInterestPayment = loanProductRelatedDetail - .graceOnInterestPayment(); - final Integer graceOnInterestCharged = loanProductRelatedDetail - .graceOnInterestCharged(); - - // Interest recalculation settings - final DaysInMonthType daysInMonthType = loanProductRelatedDetail - .fetchDaysInMonthType(); - final DaysInYearType daysInYearType = loanProductRelatedDetail - .fetchDaysInYearType(); - final boolean isInterestRecalculationEnabled = loanProductRelatedDetail - .isInterestRecalculationEnabled(); - final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; - return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, - loanTermPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentPeriodFrequencyType, - nthDay.getValue(), dayOfWeek, amortizationMethod, - interestMethod, interestRatePerPeriod, - interestRatePeriodFrequencyType, annualNominalInterestRate, - interestCalculationPeriodMethod, - allowPartialPeriodInterestCalcualtion, principalMoney, - expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, - recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, - graceOnInterestCharged, interestChargedFromDate, - inArrearsTolerance, multiDisburseLoan, emiAmount, - disbursementDatas, maxOutstandingBalance, - loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, - daysInYearType, isInterestRecalculationEnabled, - rescheduleStrategyMethod, compoundingMethod, - restCalendarInstance, recalculationFrequencyType, - compoundingCalendarInstance, compoundingFrequencyType, - principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, - approvedAmount, loanTermVariations, calendarHistoryDataWrapper, - isInterestChargedFromDateSameAsDisbursalDateEnabled, - numberOfdays, isSkipRepaymentOnFirstDayofMonth); - } - - public static LoanApplicationTerms assembleFrom( - final ApplicationCurrency applicationCurrency, - final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, - final LocalDate expectedDisbursementDate, - final LocalDate repaymentsStartingFromDate, - final LocalDate calculatedRepaymentsStartingFromDate, - final Money inArrearsTolerance, - final LoanProductRelatedDetail loanProductRelatedDetail, - final boolean multiDisburseLoan, - final BigDecimal emiAmount, - final List disbursementDatas, - final BigDecimal maxOutstandingBalance, - final LocalDate interestChargedFromDate, - final LoanInterestRecalculationDetails interestRecalculationDetails, - final CalendarInstance restCalendarInstance, - final RecalculationFrequencyType recalculationFrequencyType, - final CalendarInstance compoundingCalendarInstance, - final RecalculationFrequencyType compoundingFrequencyType, - final BigDecimal principalThresholdForLastInstalment, - final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, - final Calendar loanCalendar, BigDecimal approvedAmount, - final BigDecimal annualNominalInterestRate, - final List loanTermVariations, - Integer numberOfdays, boolean isSkipRepaymentOnFirstDayofMonth) { - - final Integer numberOfRepayments = loanProductRelatedDetail - .getNumberOfRepayments(); - final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); - final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail - .getRepaymentPeriodFrequencyType(); - final AmortizationMethod amortizationMethod = loanProductRelatedDetail - .getAmortizationMethod(); - final InterestMethod interestMethod = loanProductRelatedDetail - .getInterestMethod(); - final BigDecimal interestRatePerPeriod = loanProductRelatedDetail - .getNominalInterestRatePerPeriod(); - final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail - .getInterestPeriodFrequencyType(); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail - .getInterestCalculationPeriodMethod(); - final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail - .isAllowPartialPeriodInterestCalcualtion(); - final Money principalMoney = loanProductRelatedDetail.getPrincipal(); - - // - final Integer graceOnPrincipalPayment = loanProductRelatedDetail - .graceOnPrincipalPayment(); - final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail - .recurringMoratoriumOnPrincipalPeriods(); - final Integer graceOnInterestPayment = loanProductRelatedDetail - .graceOnInterestPayment(); - final Integer graceOnInterestCharged = loanProductRelatedDetail - .graceOnInterestCharged(); - - // Interest recalculation settings - final DaysInMonthType daysInMonthType = loanProductRelatedDetail - .fetchDaysInMonthType(); - final DaysInYearType daysInYearType = loanProductRelatedDetail - .fetchDaysInYearType(); - final boolean isInterestRecalculationEnabled = loanProductRelatedDetail - .isInterestRecalculationEnabled(); - LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; - if (isInterestRecalculationEnabled) { - rescheduleStrategyMethod = interestRecalculationDetails - .getRescheduleStrategyMethod(); - interestRecalculationCompoundingMethod = interestRecalculationDetails - .getInterestRecalculationCompoundingMethod(); - } - final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; - - return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, - loanTermPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentPeriodFrequencyType, null, null, - amortizationMethod, interestMethod, interestRatePerPeriod, - interestRatePeriodFrequencyType, annualNominalInterestRate, - interestCalculationPeriodMethod, - allowPartialPeriodInterestCalcualtion, principalMoney, - expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, - recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, - graceOnInterestCharged, interestChargedFromDate, - inArrearsTolerance, multiDisburseLoan, emiAmount, - disbursementDatas, maxOutstandingBalance, - loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, - daysInYearType, isInterestRecalculationEnabled, - rescheduleStrategyMethod, - interestRecalculationCompoundingMethod, restCalendarInstance, - recalculationFrequencyType, compoundingCalendarInstance, - compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, - approvedAmount, loanTermVariations, calendarHistoryDataWrapper, - isInterestChargedFromDateSameAsDisbursalDateEnabled, - numberOfdays, isSkipRepaymentOnFirstDayofMonth); - - } - - public static LoanApplicationTerms assembleFrom( - final LoanApplicationTerms applicationTerms, - final List loanTermVariations) { - return new LoanApplicationTerms( - applicationTerms.currency, - applicationTerms.loanTermFrequency, - applicationTerms.loanTermPeriodFrequencyType, - applicationTerms.numberOfRepayments, - applicationTerms.repaymentEvery, - applicationTerms.repaymentPeriodFrequencyType, - applicationTerms.nthDay, - applicationTerms.weekDayType, - applicationTerms.amortizationMethod, - applicationTerms.interestMethod, - applicationTerms.interestRatePerPeriod, - applicationTerms.interestRatePeriodFrequencyType, - applicationTerms.annualNominalInterestRate, - applicationTerms.interestCalculationPeriodMethod, - applicationTerms.allowPartialPeriodInterestCalcualtion, - applicationTerms.principal, - applicationTerms.expectedDisbursementDate, - applicationTerms.repaymentsStartingFromDate, - applicationTerms.calculatedRepaymentsStartingFromDate, - applicationTerms.principalGrace, - applicationTerms.recurringMoratoriumOnPrincipalPeriods, - applicationTerms.interestPaymentGrace, - applicationTerms.interestChargingGrace, - applicationTerms.interestChargedFromDate, - applicationTerms.inArrearsTolerance, - applicationTerms.multiDisburseLoan, - applicationTerms.actualFixedEmiAmount, - applicationTerms.disbursementDatas, - applicationTerms.maxOutstandingBalance, - applicationTerms.graceOnArrearsAgeing, - applicationTerms.daysInMonthType, - applicationTerms.daysInYearType, - applicationTerms.interestRecalculationEnabled, - applicationTerms.rescheduleStrategyMethod, - applicationTerms.interestRecalculationCompoundingMethod, - applicationTerms.restCalendarInstance, - applicationTerms.recalculationFrequencyType, - applicationTerms.compoundingCalendarInstance, - applicationTerms.compoundingFrequencyType, - applicationTerms.principalThresholdForLastInstalment, - applicationTerms.installmentAmountInMultiplesOf, - applicationTerms.preClosureInterestCalculationStrategy, - applicationTerms.loanCalendar, - applicationTerms.approvedPrincipal.getAmount(), - loanTermVariations, - applicationTerms.calendarHistoryDataWrapper, - applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled, - applicationTerms.numberOfDays, - applicationTerms.isSkipRepaymentOnFirstDayOfMonth); - } - - private LoanApplicationTerms( - final ApplicationCurrency currency, - final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, - final Integer numberOfRepayments, - final Integer repaymentEvery, - final PeriodFrequencyType repaymentPeriodFrequencyType, - final Integer nthDay, - final DayOfWeekType weekDayType, - final AmortizationMethod amortizationMethod, - final InterestMethod interestMethod, - final BigDecimal interestRatePerPeriod, - final PeriodFrequencyType interestRatePeriodFrequencyType, - final BigDecimal annualNominalInterestRate, - final InterestCalculationPeriodMethod interestCalculationPeriodMethod, - final boolean allowPartialPeriodInterestCalcualtion, - final Money principal, - final LocalDate expectedDisbursementDate, - final LocalDate repaymentsStartingFromDate, - final LocalDate calculatedRepaymentsStartingFromDate, - final Integer principalGrace, - final Integer recurringMoratoriumOnPrincipalPeriods, - final Integer interestPaymentGrace, - final Integer interestChargingGrace, - final LocalDate interestChargedFromDate, - final Money inArrearsTolerance, - final boolean multiDisburseLoan, - final BigDecimal emiAmount, - final List disbursementDatas, - final BigDecimal maxOutstandingBalance, - final Integer graceOnArrearsAgeing, - final DaysInMonthType daysInMonthType, - final DaysInYearType daysInYearType, - 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, - final Calendar loanCalendar, BigDecimal approvedAmount, - List loanTermVariations, - final CalendarHistoryDataWrapper calendarHistoryDataWrapper, - Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, - final Integer numberOfdays, - final boolean isSkipRepaymentOnFirstDayofMonth) { - - this.currency = currency; - this.loanTermFrequency = loanTermFrequency; - this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; - this.numberOfRepayments = numberOfRepayments; - this.repaymentEvery = repaymentEvery; - this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType; - this.nthDay = nthDay; - this.weekDayType = weekDayType; - this.amortizationMethod = amortizationMethod; - - this.interestMethod = interestMethod; - this.interestRatePerPeriod = interestRatePerPeriod; - this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType; - this.annualNominalInterestRate = annualNominalInterestRate; - this.interestCalculationPeriodMethod = interestCalculationPeriodMethod; - this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion; - - this.principal = principal; - this.expectedDisbursementDate = expectedDisbursementDate; - this.repaymentsStartingFromDate = repaymentsStartingFromDate; - this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; - - this.principalGrace = principalGrace; - this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods; - this.interestPaymentGrace = interestPaymentGrace; - this.interestChargingGrace = interestChargingGrace; - this.interestChargedFromDate = interestChargedFromDate; - - this.inArrearsTolerance = inArrearsTolerance; - this.multiDisburseLoan = multiDisburseLoan; - this.fixedEmiAmount = emiAmount; - this.actualFixedEmiAmount = emiAmount; - this.disbursementDatas = disbursementDatas; - this.maxOutstandingBalance = maxOutstandingBalance; - this.graceOnArrearsAgeing = graceOnArrearsAgeing; - this.daysInMonthType = daysInMonthType; - this.daysInYearType = daysInYearType; - this.interestRecalculationEnabled = isInterestRecalculationEnabled; - 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; - this.isSkipRepaymentOnFirstDayOfMonth = isSkipRepaymentOnFirstDayofMonth; - this.numberOfDays = numberOfdays; - - this.loanCalendar = loanCalendar; - this.approvedPrincipal = Money.of(principal.getCurrency(), - approvedAmount); - this.variationsDataWrapper = new LoanTermVariationsDataWrapper( - loanTermVariations); - this.actualNumberOfRepayments = numberOfRepayments - + getLoanTermVariations().adjustNumberOfRepayments(); - this.adjustPrincipalForFlatLoans = principal.zero(); - if (this.calculatedRepaymentsStartingFromDate == null) { - this.seedDate = this.expectedDisbursementDate; - } else { - this.seedDate = this.calculatedRepaymentsStartingFromDate; - } - this.calendarHistoryDataWrapper = calendarHistoryDataWrapper; - this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled; - } - - public Money adjustPrincipalIfLastRepaymentPeriod( - final Money principalForPeriod, - final Money totalCumulativePrincipalToDate, final int periodNumber) { - - Money adjusted = principalForPeriod; - - final Money totalPrincipalRemaining = this.principal - .minus(totalCumulativePrincipalToDate); - if (totalPrincipalRemaining.isLessThanZero()) { - // paid too much principal, subtract amount that overpays from - // principal paid for period. - adjusted = principalForPeriod.minus(totalPrincipalRemaining.abs()); - } else if (this.actualFixedEmiAmount != null) { - final Money difference = this.principal - .minus(totalCumulativePrincipalToDate); - final Money principalThreshold = principalForPeriod.multipliedBy( - this.principalThresholdForLastInstalment).dividedBy(100, - MoneyHelper.getRoundingMode()); - if (difference.isLessThan(principalThreshold)) { - adjusted = principalForPeriod.plus(difference.abs()); - } - } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, - periodNumber)) { - - final Money difference = totalCumulativePrincipalToDate - .minus(this.principal); - if (difference.isLessThanZero()) { - adjusted = principalForPeriod.plus(difference.abs()); - } else if (difference.isGreaterThanZero()) { - adjusted = principalForPeriod.minus(difference.abs()); - } - } - - return adjusted; - } - - public Money adjustInterestIfLastRepaymentPeriod( - final Money interestForThisPeriod, - final Money totalCumulativeInterestToDate, - final Money totalInterestDueForLoan, final int periodNumber) { - - Money adjusted = interestForThisPeriod; - - final Money totalInterestRemaining = totalInterestDueForLoan - .minus(totalCumulativeInterestToDate); - if (totalInterestRemaining.isLessThanZero()) { - // paid too much interest, subtract amount that overpays from - // interest paid for period. - adjusted = interestForThisPeriod - .minus(totalInterestRemaining.abs()); - } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, - periodNumber)) { - final Money interestDifference = totalCumulativeInterestToDate - .minus(totalInterestDueForLoan); - if (interestDifference.isLessThanZero()) { - adjusted = interestForThisPeriod.plus(interestDifference.abs()); - } else if (interestDifference.isGreaterThanZero()) { - adjusted = interestForThisPeriod - .minus(interestDifference.abs()); - } - } - if (adjusted.isLessThanZero()) { - adjusted = adjusted.plus(adjusted); - } - return adjusted; - } - - /** - * Calculates the total interest to be charged on loan taking into account - * grace settings. - * - */ - public Money calculateTotalInterestCharged( - final PaymentPeriodsInOneYearCalculator calculator, - final MathContext mc) { - - Money totalInterestCharged = this.principal.zero(); - - switch (this.interestMethod) { - case FLAT: - final Money totalInterestChargedForLoanTerm = calculateTotalFlatInterestDueWithoutGrace( - calculator, mc); - - final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace( - calculator, mc); - - final Money totalGraceOnInterestCharged = totalInterestPerInstallment - .multiplyRetainScale(getInterestChargingGrace(), - mc.getRoundingMode()); - - totalInterestCharged = totalInterestChargedForLoanTerm - .minus(totalGraceOnInterestCharged); - break; - case DECLINING_BALANCE: - case INVALID: - break; - } - - return totalInterestCharged; - } - - public Money calculateTotalPrincipalForPeriod( - final PaymentPeriodsInOneYearCalculator calculator, - final Money outstandingBalance, final int periodNumber, - final MathContext mc, Money interestForThisInstallment) { - - Money principalForInstallment = this.principal.zero(); - - switch (this.interestMethod) { - case FLAT: - principalForInstallment = calculateTotalPrincipalPerPeriodWithoutGrace( - mc, periodNumber); - break; - case DECLINING_BALANCE: - switch (this.amortizationMethod) { - case EQUAL_INSTALLMENTS: - Money totalPmtForThisInstallment = pmtForInstallment( - calculator, outstandingBalance, periodNumber, mc); - principalForInstallment = calculatePrincipalDueForInstallment( - periodNumber, totalPmtForThisInstallment, - interestForThisInstallment); - break; - case EQUAL_PRINCIPAL: - principalForInstallment = calculateEqualPrincipalDueForInstallment( - mc, periodNumber); - break; - case INVALID: - break; - } - break; - case INVALID: - break; - } - - return principalForInstallment; - } - - public Money pmtForInstallment( - final PaymentPeriodsInOneYearCalculator calculator, - final Money outstandingBalance, final int periodNumber, - final MathContext mc) { - // Calculate exact period from disbursement date - final LocalDate periodStartDate = getExpectedDisbursementDate() - .withDayOfMonth(1); - final LocalDate periodEndDate = getPeriodEndDate(periodStartDate); - // equal installments - final int periodsElapsed = periodNumber - 1; - // with periodic interest for default month and year for - // equal installment - final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate( - calculator, mc, DaysInMonthType.DAYS_30, - DaysInYearType.DAYS_365, periodStartDate, periodEndDate); - Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod( - periodicInterestRateForRepaymentPeriod, outstandingBalance, - periodsElapsed); - return totalPmtForThisInstallment; - } - - private LocalDate getPeriodEndDate(final LocalDate startDate) { - LocalDate dueRepaymentPeriodDate = startDate; - switch (this.repaymentPeriodFrequencyType) { - case DAYS: - dueRepaymentPeriodDate = startDate.plusDays(this.repaymentEvery); - break; - case WEEKS: - dueRepaymentPeriodDate = startDate.plusWeeks(this.repaymentEvery); - break; - case MONTHS: - dueRepaymentPeriodDate = startDate.plusMonths(this.repaymentEvery); - break; - case YEARS: - dueRepaymentPeriodDate = startDate.plusYears(this.repaymentEvery); - break; - case INVALID: - break; - } - return dueRepaymentPeriodDate; - } - - public PrincipalInterest calculateTotalInterestForPeriod( - final PaymentPeriodsInOneYearCalculator calculator, - final double interestCalculationGraceOnRepaymentPeriodFraction, - final int periodNumber, final MathContext mc, - final Money cumulatingInterestPaymentDueToGrace, - final Money outstandingBalance, final LocalDate periodStartDate, - final LocalDate periodEndDate) { - - Money interestForInstallment = this.principal.zero(); - Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace - .copy(); - - switch (this.interestMethod) { - case FLAT: - - switch (this.amortizationMethod) { - case EQUAL_INSTALLMENTS: - // average out outstanding interest over remaining - // instalments where interest is applicable - interestForInstallment = calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods( - calculator, periodNumber, mc); - break; - case EQUAL_PRINCIPAL: - // interest follows time-value of money and is brought - // forward to next applicable interest payment period - final PrincipalInterest result = calculateTotalFlatInterestForPeriod( - calculator, periodNumber, mc, - interestBroughtForwardDueToGrace); - interestForInstallment = result.interest(); - interestBroughtForwardDueToGrace = result - .interestPaymentDueToGrace(); - break; - case INVALID: - break; - } - break; - case DECLINING_BALANCE: - - final Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace( - calculator, mc, outstandingBalance, periodStartDate, - periodEndDate); - - final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace( - calculator, - interestCalculationGraceOnRepaymentPeriodFraction, mc, - outstandingBalance, periodNumber, periodStartDate, - periodEndDate); - - interestForInstallment = interestForThisInstallmentAfterGrace; - if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) { - interestForInstallment = interestBroughtForwardDueToGrace - .plus(interestForThisInstallmentAfterGrace); - interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace - .zero(); - } else if (isInterestFreeGracePeriod(periodNumber)) { - interestForInstallment = interestForInstallment.zero(); - } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { - interestForInstallment = interestForThisInstallmentAfterGrace; - } else { - interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace - .plus(interestForThisInstallmentBeforeGrace); - } - break; - case INVALID: - break; - } - - return new PrincipalInterest(null, interestForInstallment, - interestBroughtForwardDueToGrace); - } - - private final boolean isLastRepaymentPeriod(final int numberOfRepayments, - final int periodNumber) { - return periodNumber == numberOfRepayments; - } - - /** - * general method to calculate totalInterestDue discounting any grace - * settings - */ - private Money calculateTotalFlatInterestDueWithoutGrace( - final PaymentPeriodsInOneYearCalculator calculator, - final MathContext mc) { - - Money totalInterestDue = this.principal.zero(); - - switch (this.interestMethod) { - case FLAT: - final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm( - calculator, mc); - totalInterestDue = this.principal.multiplyRetainScale( - interestRateForLoanTerm, mc.getRoundingMode()); - - break; - case DECLINING_BALANCE: - break; - case INVALID: - break; - } - - if (this.totalInterestDue != null) { - totalInterestDue = this.totalInterestDue; - } - - return totalInterestDue; - } - - private BigDecimal calculateFlatInterestRateForLoanTerm( - final PaymentPeriodsInOneYearCalculator calculator, - final MathContext mc) { - - final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); - - final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); - final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal - .valueOf(loanTermPeriodsInOneYear); - - final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm(); - - return this.annualNominalInterestRate - .divide(loanTermPeriodsInYearBigDecimal, mc) - .divide(divisor, mc).multiply(loanTermFrequencyBigDecimal); - } - - private BigDecimal calculatePeriodsInLoanTerm() { - - BigDecimal periodsInLoanTerm = BigDecimal - .valueOf(this.loanTermFrequency); - switch (this.interestCalculationPeriodMethod) { - case DAILY: - // number of days from 'ideal disbursement' to final date - - LocalDate loanStartDate = getExpectedDisbursementDate(); - if (getInterestChargedFromDate() != null - && loanStartDate - .isBefore(getInterestChargedFromLocalDate())) { - loanStartDate = getInterestChargedFromLocalDate(); - } - - final int periodsInLoanTermInteger = Days.daysBetween( - loanStartDate, this.loanEndDate).getDays(); - periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger); - break; - case INVALID: - break; - case SAME_AS_REPAYMENT_PERIOD: - if (this.allowPartialPeriodInterestCalcualtion) { - LocalDate startDate = getExpectedDisbursementDate(); - if (getInterestChargedFromDate() != null) { - startDate = getInterestChargedFromLocalDate(); - } - periodsInLoanTerm = calculatePeriodsBetweenDates(startDate, - this.loanEndDate); - } - break; - } - - return periodsInLoanTerm; - } - - public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, - final LocalDate endDate) { - BigDecimal numberOfPeriods = BigDecimal.ZERO; - switch (this.repaymentPeriodFrequencyType) { - case DAYS: - int numberOfDays = Days.daysBetween(startDate, endDate).getDays(); - numberOfPeriods = BigDecimal.valueOf((double) numberOfDays); - break; - case WEEKS: - int numberOfWeeks = Weeks.weeksBetween(startDate, endDate) - .getWeeks(); - int daysLeftAfterWeeks = Days.daysBetween( - startDate.plusWeeks(numberOfWeeks), endDate).getDays(); - numberOfPeriods = numberOfPeriods.add( - BigDecimal.valueOf(numberOfWeeks)).add( - BigDecimal.valueOf((double) daysLeftAfterWeeks / 7)); - break; - case MONTHS: - int numberOfMonths = Months.monthsBetween(startDate, endDate) - .getMonths(); - LocalDate startDateAfterConsideringMonths = startDate - .plusMonths(numberOfMonths); - LocalDate endDateAfterConsideringMonths = startDate - .plusMonths(numberOfMonths + 1); - int daysLeftAfterMonths = Days.daysBetween( - startDateAfterConsideringMonths, endDate).getDays(); - int daysInPeriodAfterMonths = Days.daysBetween( - startDateAfterConsideringMonths, - endDateAfterConsideringMonths).getDays(); - numberOfPeriods = numberOfPeriods.add( - BigDecimal.valueOf(numberOfMonths)).add( - BigDecimal.valueOf((double) daysLeftAfterMonths - / daysInPeriodAfterMonths)); - break; - case YEARS: - int numberOfYears = Years.yearsBetween(startDate, endDate) - .getYears(); - LocalDate startDateAfterConsideringYears = startDate - .plusYears(numberOfYears); - LocalDate endDateAfterConsideringYears = startDate - .plusYears(numberOfYears + 1); - int daysLeftAfterYears = Days.daysBetween( - startDateAfterConsideringYears, endDate).getDays(); - int daysInPeriodAfterYears = Days.daysBetween( - startDateAfterConsideringYears, - endDateAfterConsideringYears).getDays(); - numberOfPeriods = numberOfPeriods.add( - BigDecimal.valueOf(numberOfYears)).add( - BigDecimal.valueOf((double) daysLeftAfterYears - / daysInPeriodAfterYears)); - break; - default: - break; - } - return numberOfPeriods; - } - - public void updateLoanEndDate(final LocalDate loanEndDate) { - this.loanEndDate = loanEndDate; - } - - private Money calculateTotalInterestPerInstallmentWithoutGrace( - final PaymentPeriodsInOneYearCalculator calculator, - final MathContext mc) { - - final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace( - calculator, mc); - - return totalInterestForLoanTerm.dividedBy( - Long.valueOf(this.actualNumberOfRepayments), - mc.getRoundingMode()); - } - - private Money calculateTotalPrincipalPerPeriodWithoutGrace( - final MathContext mc, final int periodNumber) { - final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment(); - Money principalPerPeriod = this.principal.dividedBy( - totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus( - this.adjustPrincipalForFlatLoans); - if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { - principalPerPeriod = principalPerPeriod.zero(); - } - if (!isPrincipalGraceApplicableForThisPeriod(periodNumber) - && currentPeriodFixedPrincipalAmount != null) { - this.adjustPrincipalForFlatLoans = this.adjustPrincipalForFlatLoans - .plus(principalPerPeriod.minus( - currentPeriodFixedPrincipalAmount).dividedBy( - this.actualNumberOfRepayments - periodNumber, - mc.getRoundingMode())); - principalPerPeriod = this.principal.zero().plus( - currentPeriodFixedPrincipalAmount); - - } - return principalPerPeriod; - } - - private PrincipalInterest calculateTotalFlatInterestForPeriod( - final PaymentPeriodsInOneYearCalculator calculator, - final int periodNumber, final MathContext mc, - final Money cumulatingInterestPaymentDueToGrace) { - - Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace - .copy(); - - Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace( - calculator, mc); - if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { - interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace - .plus(interestForInstallment); - interestForInstallment = interestForInstallment.zero(); - } else if (isInterestFreeGracePeriod(periodNumber)) { - interestForInstallment = interestForInstallment.zero(); - } else if (isFirstPeriodAfterInterestPaymentGracePeriod(periodNumber)) { - interestForInstallment = cumulatingInterestPaymentDueToGrace - .plus(interestForInstallment); - interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace - .zero(); - } - - return new PrincipalInterest(null, interestForInstallment, - interestBroughtForwardDueToGrace); - } - - /* - * calculates the interest that should be due for a given scheduled loan - * repayment period. It takes into account GRACE periods and calculates how - * much interest is due per period by averaging the number of periods where - * interest is due and should be paid against the total known interest that - * is due without grace. - */ - private Money calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods( - final PaymentPeriodsInOneYearCalculator calculator, - final int periodNumber, final MathContext mc) { - - Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace( - calculator, mc); - if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { - interestForInstallment = interestForInstallment.zero(); - } else if (isInterestFreeGracePeriod(periodNumber)) { - interestForInstallment = interestForInstallment.zero(); - } else { - - final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace( - calculator, mc); - - final Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace( - calculator, mc); - - final Money totalInterestFree = interestPerGracePeriod - .multipliedBy(getInterestChargingGrace()); - final Money realTotalInterestForLoan = totalInterestForLoanTerm - .minus(totalInterestFree); - - final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments); - - interestForInstallment = realTotalInterestForLoan.dividedBy( - BigDecimal.valueOf(interestPaymentDuePeriods), - mc.getRoundingMode()); - } - - return interestForInstallment; - } - - private BigDecimal periodicInterestRate( - final PaymentPeriodsInOneYearCalculator calculator, - final MathContext mc, final DaysInMonthType daysInMonthType, - final DaysInYearType daysInYearType, LocalDate periodStartDate, - LocalDate periodEndDate) { - - final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); - - final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); - final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal - .valueOf(loanTermPeriodsInOneYear); - - BigDecimal periodicInterestRate = BigDecimal.ZERO; - BigDecimal loanTermFrequencyBigDecimal = calculateLoanTermFrequency( - periodStartDate, periodEndDate); - switch (this.interestCalculationPeriodMethod) { - case INVALID: - break; - case DAILY: - // For daily work out number of days in the period - BigDecimal numberOfDaysInPeriod = BigDecimal.valueOf(Days - .daysBetween(periodStartDate, periodEndDate).getDays()); - - final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate - .divide(loanTermPeriodsInYearBigDecimal, mc).divide( - divisor, mc); - - switch (this.repaymentPeriodFrequencyType) { - case INVALID: - break; - case DAYS: - periodicInterestRate = oneDayOfYearInterestRate.multiply( - numberOfDaysInPeriod, mc); - break; - case WEEKS: - periodicInterestRate = oneDayOfYearInterestRate.multiply( - numberOfDaysInPeriod, mc); - break; - case MONTHS: - if (daysInMonthType.isDaysInMonth_30()) { - numberOfDaysInPeriod = loanTermFrequencyBigDecimal - .multiply(BigDecimal.valueOf(30), mc); - } - periodicInterestRate = oneDayOfYearInterestRate.multiply( - numberOfDaysInPeriod, mc); - break; - case YEARS: - switch (daysInYearType) { - case DAYS_360: - numberOfDaysInPeriod = loanTermFrequencyBigDecimal - .multiply(BigDecimal.valueOf(360), mc); - break; - case DAYS_364: - numberOfDaysInPeriod = loanTermFrequencyBigDecimal - .multiply(BigDecimal.valueOf(364), mc); - break; - case DAYS_365: - numberOfDaysInPeriod = loanTermFrequencyBigDecimal - .multiply(BigDecimal.valueOf(365), mc); - break; - default: - break; - } - periodicInterestRate = oneDayOfYearInterestRate.multiply( - numberOfDaysInPeriod, mc); - break; - } - break; - case SAME_AS_REPAYMENT_PERIOD: - periodicInterestRate = this.annualNominalInterestRate - .divide(loanTermPeriodsInYearBigDecimal, mc) - .divide(divisor, mc).multiply(loanTermFrequencyBigDecimal); - break; - } - - return periodicInterestRate; - } - - private BigDecimal calculateLoanTermFrequency( - final LocalDate periodStartDate, final LocalDate periodEndDate) { - BigDecimal loanTermFrequencyBigDecimal = BigDecimal - .valueOf(this.repaymentEvery); - if (this.interestCalculationPeriodMethod.isDaily() - || this.allowPartialPeriodInterestCalcualtion) { - loanTermFrequencyBigDecimal = calculatePeriodsBetweenDates( - periodStartDate, periodEndDate); - } - return loanTermFrequencyBigDecimal; - } - - public BigDecimal interestRateFor( - final PaymentPeriodsInOneYearCalculator calculator, - final MathContext mc, final Money outstandingBalance, - final LocalDate fromDate, final LocalDate toDate) { - - long loanTermPeriodsInOneYear = calculator.calculate( - PeriodFrequencyType.DAYS).longValue(); - int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays(); - if (isFallingInRepaymentPeriod(fromDate, toDate)) { - loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); - repaymentEvery = getPeriodsBetween(fromDate, toDate); - } - - final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); - final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal - .valueOf(loanTermPeriodsInOneYear); - final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate - .divide(loanTermPeriodsInYearBigDecimal, mc) - .divide(divisor, mc); - BigDecimal interestRate = oneDayOfYearInterestRate.multiply( - BigDecimal.valueOf(repaymentEvery), mc); - return outstandingBalance.getAmount().multiply(interestRate, mc); - } - - private long calculatePeriodsInOneYear( - final PaymentPeriodsInOneYearCalculator calculator) { - - // check if daysInYears is set if so change periodsInOneYear to days set - // in db - long periodsInOneYear; - boolean daysInYearToUse = (this.repaymentPeriodFrequencyType.getCode() - .equalsIgnoreCase("periodFrequencyType.days") && !this.daysInYearType - .getCode().equalsIgnoreCase("DaysInYearType.actual")); - if (daysInYearToUse) { - periodsInOneYear = this.daysInYearType.getValue().longValue(); - } else { - periodsInOneYear = calculator.calculate( - this.repaymentPeriodFrequencyType).longValue(); - } - switch (this.interestCalculationPeriodMethod) { - case DAILY: - periodsInOneYear = (!this.daysInYearType.getCode() - .equalsIgnoreCase("DaysInYearType.actual")) ? this.daysInYearType - .getValue().longValue() : calculator.calculate( - PeriodFrequencyType.DAYS).longValue(); - break; - case INVALID: - break; - case SAME_AS_REPAYMENT_PERIOD: - break; - } - - return periodsInOneYear; - } - - private int calculateNumberOfRepaymentsWithPrincipalPayment() { - int numPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( - this.actualNumberOfRepayments, - this.getRecurringMoratoriumOnPrincipalPeriods(), - this.getPrincipalGrace(), 0); - return numPeriods; - } - - private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue( - final Integer totalNumberOfRepaymentPeriods) { - return totalNumberOfRepaymentPeriods - - Math.max(getInterestChargingGrace(), - getInterestPaymentGrace()); - } - - public boolean isPrincipalGraceApplicableForThisPeriod( - final int periodNumber) { - if (this.getRecurringMoratoriumOnPrincipalPeriods() > 0) { - return ((periodNumber > 0 && periodNumber <= getPrincipalGrace()) || (periodNumber > 0 && (((periodNumber - getPrincipalGrace()) % (this - .getRecurringMoratoriumOnPrincipalPeriods() + 1)) != 1))); - } else { - return periodNumber > 0 && periodNumber <= getPrincipalGrace(); - } - } - - private boolean isInterestPaymentGraceApplicableForThisPeriod( - final int periodNumber) { - return periodNumber > 0 && periodNumber <= getInterestPaymentGrace(); - } - - private boolean isFirstPeriodAfterInterestPaymentGracePeriod( - final int periodNumber) { - return periodNumber > 0 - && periodNumber == getInterestPaymentGrace() + 1; - } - - private boolean isInterestFreeGracePeriod(final int periodNumber) { - return periodNumber > 0 && periodNumber <= getInterestChargingGrace(); - } - - public Integer getPrincipalGrace() { - Integer graceOnPrincipalPayments = Integer.valueOf(0); - if (this.principalGrace != null) { - graceOnPrincipalPayments = this.principalGrace; - } - return graceOnPrincipalPayments; - } - - public Integer getRecurringMoratoriumOnPrincipalPeriods() { - Integer recurringMoratoriumOnPrincipalPeriods = Integer.valueOf(0); - if (this.recurringMoratoriumOnPrincipalPeriods != null) { - recurringMoratoriumOnPrincipalPeriods = this.recurringMoratoriumOnPrincipalPeriods; - } - return recurringMoratoriumOnPrincipalPeriods; - } - - public Integer getInterestPaymentGrace() { - Integer graceOnInterestPayments = Integer.valueOf(0); - if (this.interestPaymentGrace != null) { - graceOnInterestPayments = this.interestPaymentGrace; - } - return graceOnInterestPayments; - } - - public Integer getInterestChargingGrace() { - Integer graceOnInterestCharged = Integer.valueOf(0); - if (this.interestChargingGrace != null) { - graceOnInterestCharged = this.interestChargingGrace; - } - return graceOnInterestCharged; - } - - private double paymentPerPeriod(final BigDecimal periodicInterestRate, - final Money balance, final int periodsElapsed) { - - if (getFixedEmiAmount() == null) { - final double futureValue = 0; - final double principalDouble = balance.getAmount() - .multiply(BigDecimal.valueOf(-1)).doubleValue(); - - final Integer periodsRemaining = calculateNumberOfRemainingPrincipalPaymentPeriods( - this.actualNumberOfRepayments, - this.getRecurringMoratoriumOnPrincipalPeriods(), - this.getPrincipalGrace(), periodsElapsed); - - double installmentAmount = FinanicalFunctions.pmt( - periodicInterestRate.doubleValue(), - periodsRemaining.doubleValue(), principalDouble, - futureValue, false); - - if (this.installmentAmountInMultiplesOf != null) { - installmentAmount = Money.roundToMultiplesOf(installmentAmount, - this.installmentAmountInMultiplesOf); - } - setFixedEmiAmount(BigDecimal.valueOf(installmentAmount)); - } - return getFixedEmiAmount().doubleValue(); - } - - private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace( - final PaymentPeriodsInOneYearCalculator calculator, - final MathContext mc, final Money outstandingBalance, - LocalDate periodStartDate, LocalDate periodEndDate) { - - Money interestDue = Money.zero(outstandingBalance.getCurrency()); - - final BigDecimal periodicInterestRate = periodicInterestRate( - calculator, mc, this.daysInMonthType, this.daysInYearType, - periodStartDate, periodEndDate); - interestDue = outstandingBalance.multiplyRetainScale( - periodicInterestRate, mc.getRoundingMode()); - - return interestDue; - } - - private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace( - final PaymentPeriodsInOneYearCalculator calculator, - final double interestCalculationGraceOnRepaymentPeriodFraction, - final MathContext mc, final Money outstandingBalance, - final int periodNumber, LocalDate periodStartDate, - LocalDate periodEndDate) { - - Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace( - calculator, mc, outstandingBalance, periodStartDate, - periodEndDate); - - if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { - interest = interest.zero(); - } - - Double fraction = interestCalculationGraceOnRepaymentPeriodFraction; - - if (isInterestFreeGracePeriod(periodNumber)) { - interest = interest.zero(); - } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { - - if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer - .valueOf(1).doubleValue()) { - interest = interest.zero(); - fraction = fraction - Integer.valueOf(1).doubleValue(); - - } else if (interestCalculationGraceOnRepaymentPeriodFraction > Double - .valueOf("0.25") - && interestCalculationGraceOnRepaymentPeriodFraction < Integer - .valueOf(1).doubleValue()) { - - final Money graceOnInterestForRepaymentPeriod = interest - .multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction); - interest = interest.minus(graceOnInterestForRepaymentPeriod); - fraction = Double.valueOf("0"); - } - } - - return interest; - } - - private boolean isInterestFreeGracePeriodFromDate( - final double interestCalculationGraceOnRepaymentPeriodFraction) { - return this.interestChargedFromDate != null - && interestCalculationGraceOnRepaymentPeriodFraction > Double - .valueOf("0.0"); - } - - private Money calculateEqualPrincipalDueForInstallment( - final MathContext mc, final int periodNumber) { - Money principal = this.principal; - if (this.fixedPrincipalAmount == null) { - final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( - this.actualNumberOfRepayments, - this.getRecurringMoratoriumOnPrincipalPeriods(), - this.getPrincipalGrace(), periodNumber); - principal = this.principal.dividedBy( - numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); - this.fixedPrincipalAmount = principal.getAmount(); - } - principal = Money.of(getCurrency(), getFixedPrincipalAmount()); - - if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { - principal = principal.zero(); - } - return principal; - } - - public void updateFixedPrincipalAmount(final MathContext mc, - final int periodNumber, final Money outstandingAmount) { - final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( - this.actualNumberOfRepayments, - this.getRecurringMoratoriumOnPrincipalPeriods(), - this.getPrincipalGrace(), periodNumber - 1); - Money principal = outstandingAmount.dividedBy( - numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); - this.fixedPrincipalAmount = principal.getAmount(); - } - - private static Integer calculateNumberOfRemainingPrincipalPaymentPeriods( - final Integer totalNumberOfRepaymentPeriods, - final int recurringMoratoriumOnPrincipalPeriods, - final int PrincipalGrace, int periodsElapsed) { - if (PrincipalGrace > periodsElapsed) { - periodsElapsed = PrincipalGrace; - } - Integer periodsRemaining = totalNumberOfRepaymentPeriods - - periodsElapsed; - if (recurringMoratoriumOnPrincipalPeriods > 0) { - double periodsRemainingDouble = ((double) periodsRemaining / ((double) (recurringMoratoriumOnPrincipalPeriods + 1))); - periodsRemaining = (int) Math.ceil(periodsRemainingDouble); - } - return periodsRemaining; - } - - public void setFixedPrincipalAmount(BigDecimal fixedPrincipalAmount) { - this.fixedPrincipalAmount = fixedPrincipalAmount; - } - - private Money calculatePrincipalDueForInstallment(final int periodNumber, - final Money totalDuePerInstallment, final Money periodInterest) { - - Money principal = totalDuePerInstallment.minus(periodInterest); - if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { - principal = principal.zero(); - } - return principal; - } - - private Money calculateTotalDueForEqualInstallmentRepaymentPeriod( - final BigDecimal periodicInterestRate, final Money balance, - final int periodsElapsed) { - - final double paymentPerRepaymentPeriod = paymentPerPeriod( - periodicInterestRate, balance, periodsElapsed); - - return Money.of(balance.getCurrency(), - BigDecimal.valueOf(paymentPerRepaymentPeriod)); - } - - public LoanProductRelatedDetail toLoanProductRelatedDetail() { - final MonetaryCurrency currency = new MonetaryCurrency( - this.currency.getCode(), this.currency.getDecimalPlaces(), - this.currency.getCurrencyInMultiplesOf()); - - return LoanProductRelatedDetail.createFrom(currency, - this.principal.getAmount(), this.interestRatePerPeriod, - this.interestRatePeriodFrequencyType, - this.annualNominalInterestRate, this.interestMethod, - this.interestCalculationPeriodMethod, - this.allowPartialPeriodInterestCalcualtion, - this.repaymentEvery, this.repaymentPeriodFrequencyType, - this.numberOfRepayments, this.principalGrace, - this.recurringMoratoriumOnPrincipalPeriods, - this.interestPaymentGrace, this.interestChargingGrace, - this.amortizationMethod, this.inArrearsTolerance.getAmount(), - this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), - this.daysInYearType.getValue(), - this.interestRecalculationEnabled); - } - - public Integer getLoanTermFrequency() { - return this.loanTermFrequency; - } - - public PeriodFrequencyType getLoanTermPeriodFrequencyType() { - return this.loanTermPeriodFrequencyType; - } - - public Integer getRepaymentEvery() { - return this.repaymentEvery; - } - - public PeriodFrequencyType getRepaymentPeriodFrequencyType() { - return this.repaymentPeriodFrequencyType; - } - - public Date getRepaymentStartFromDate() { - Date dateValue = null; - if (this.repaymentsStartingFromDate != null) { - dateValue = this.repaymentsStartingFromDate.toDate(); - } - return dateValue; - } - - public Date getInterestChargedFromDate() { - Date dateValue = null; - if (this.interestChargedFromDate != null) { - dateValue = this.interestChargedFromDate.toDate(); - } - return dateValue; - } - - public void setPrincipal(Money principal) { - this.principal = principal; - } - - public LocalDate getInterestChargedFromLocalDate() { - return this.interestChargedFromDate; - } - - public InterestMethod getInterestMethod() { - return this.interestMethod; - } - - public AmortizationMethod getAmortizationMethod() { - return this.amortizationMethod; - } - - public MonetaryCurrency getCurrency() { - return this.principal.getCurrency(); - } - - public Integer getNumberOfRepayments() { - return this.numberOfRepayments; - } - - public LocalDate getExpectedDisbursementDate() { - return this.expectedDisbursementDate; - } - - public LocalDate getRepaymentsStartingFromLocalDate() { - return this.repaymentsStartingFromDate; - } - - public LocalDate getCalculatedRepaymentsStartingFromLocalDate() { - return this.calculatedRepaymentsStartingFromDate; - } - - public Money getPrincipal() { - return this.principal; - } - - public Money getApprovedPrincipal() { - return this.approvedPrincipal; - } - - public List getDisbursementDatas() { - return this.disbursementDatas; - } - - public boolean isMultiDisburseLoan() { - return this.multiDisburseLoan; - } - - public Money getMaxOutstandingBalance() { - return Money.of(getCurrency(), this.maxOutstandingBalance); - } - - public BigDecimal getFixedEmiAmount() { - BigDecimal fixedEmiAmount = this.fixedEmiAmount; - if (getCurrentPeriodFixedEmiAmount() != null) { - fixedEmiAmount = getCurrentPeriodFixedEmiAmount(); - } - return fixedEmiAmount; - } - - public Integer getNthDay() { - return this.nthDay; - } - - public DayOfWeekType getWeekDayType() { - return this.weekDayType; - } - - public void setFixedEmiAmount(BigDecimal fixedEmiAmount) { - this.fixedEmiAmount = fixedEmiAmount; - } - - public void resetFixedEmiAmount() { - this.fixedEmiAmount = this.actualFixedEmiAmount; - } - - public LoanRescheduleStrategyMethod getLoanRescheduleStrategyMethod() { - return LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT; - } - - public boolean isInterestRecalculationEnabled() { - return this.interestRecalculationEnabled; - } - - public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() { - return this.rescheduleStrategyMethod; - } - - public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() { - return this.interestRecalculationCompoundingMethod; - } - - public CalendarInstance getRestCalendarInstance() { - return this.restCalendarInstance; - } - - private boolean isFallingInRepaymentPeriod(LocalDate fromDate, - LocalDate toDate) { - boolean isSameAsRepaymentPeriod = false; - if (this.interestCalculationPeriodMethod.getValue().equals( - InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD - .getValue())) { - switch (this.repaymentPeriodFrequencyType) { - case WEEKS: - int days = Days.daysBetween(fromDate, toDate).getDays(); - isSameAsRepaymentPeriod = (days % 7) == 0; - break; - case MONTHS: - boolean isFromDateOnEndDate = false; - if (fromDate.getDayOfMonth() > fromDate.plusDays(1) - .getDayOfMonth()) { - isFromDateOnEndDate = true; - } - boolean isToDateOnEndDate = false; - if (toDate.getDayOfMonth() > toDate.plusDays(1).getDayOfMonth()) { - isToDateOnEndDate = true; - } - - if (isFromDateOnEndDate && isToDateOnEndDate) { - isSameAsRepaymentPeriod = true; - } else { - - int months = getPeriodsBetween(fromDate, toDate); - fromDate = fromDate.plusMonths(months); - isSameAsRepaymentPeriod = fromDate.isEqual(toDate); - } - - break; - default: - break; - } - } - return isSameAsRepaymentPeriod; - } - - private Integer getPeriodsBetween(LocalDate fromDate, LocalDate toDate) { - Integer numberOfPeriods = 0; - PeriodType periodType = PeriodType.yearMonthDay(); - Period difference = new Period(fromDate, toDate, periodType); - switch (this.repaymentPeriodFrequencyType) { - case DAYS: - numberOfPeriods = difference.getDays(); - break; - case WEEKS: - periodType = PeriodType.weeks(); - difference = new Period(fromDate, toDate, periodType); - numberOfPeriods = difference.getWeeks(); - break; - case MONTHS: - numberOfPeriods = difference.getMonths(); - break; - case YEARS: - numberOfPeriods = difference.getYears(); - break; - default: - break; - } - return numberOfPeriods; - } - - public RecalculationFrequencyType getRecalculationFrequencyType() { - return this.recalculationFrequencyType; - } - - public void updateNumberOfRepayments(final Integer numberOfRepayments) { - this.numberOfRepayments = numberOfRepayments; - this.actualNumberOfRepayments = numberOfRepayments - + getLoanTermVariations().adjustNumberOfRepayments(); - - } - - public void updatePrincipalGrace(final Integer principalGrace) { - this.principalGrace = principalGrace; - } - - public void updateInterestPaymentGrace(final Integer interestPaymentGrace) { - this.interestPaymentGrace = interestPaymentGrace; - } - - public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) { - if (interestRatePerPeriod != null) { - this.interestRatePerPeriod = interestRatePerPeriod; - } - } - - public void updateAnnualNominalInterestRate( - BigDecimal annualNominalInterestRate) { - if (annualNominalInterestRate != null) { - this.annualNominalInterestRate = annualNominalInterestRate; - } - } - - public BigDecimal getAnnualNominalInterestRate() { - return this.annualNominalInterestRate; - } - - public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) { - if (interestChargedFromDate != null) { - this.interestChargedFromDate = interestChargedFromDate; - } - } - - public void updateLoanTermFrequency(Integer loanTermFrequency) { - if (loanTermFrequency != null) { - this.loanTermFrequency = loanTermFrequency; - } - } - - public void updateTotalInterestDue(Money totalInterestDue) { - - if (totalInterestDue != null) { - this.totalInterestDue = totalInterestDue; - } - } - - public ApplicationCurrency getApplicationCurrency() { - return this.currency; - } - - public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { - return this.interestCalculationPeriodMethod; - } - - public LoanPreClosureInterestCalculationStrategy getPreClosureInterestCalculationStrategy() { - return this.preClosureInterestCalculationStrategy; - } - - public CalendarInstance getCompoundingCalendarInstance() { - return this.compoundingCalendarInstance; - } - - public RecalculationFrequencyType getCompoundingFrequencyType() { - return this.compoundingFrequencyType; - } - - public BigDecimal getActualFixedEmiAmount() { - return this.actualFixedEmiAmount; - } - - public Calendar getLoanCalendar() { - return loanCalendar; - } - - public BigDecimal getFixedPrincipalAmount() { - BigDecimal fixedPrincipalAmount = this.fixedPrincipalAmount; - if (getCurrentPeriodFixedPrincipalAmount() != null) { - fixedPrincipalAmount = getCurrentPeriodFixedPrincipalAmount(); - } - return fixedPrincipalAmount; - } - - public LoanTermVariationsDataWrapper getLoanTermVariations() { - return this.variationsDataWrapper; - } - - public BigDecimal getCurrentPeriodFixedEmiAmount() { - return this.currentPeriodFixedEmiAmount; - } - - public void setCurrentPeriodFixedEmiAmount( - BigDecimal currentPeriodFixedEmiAmount) { - this.currentPeriodFixedEmiAmount = currentPeriodFixedEmiAmount; - } - - public BigDecimal getCurrentPeriodFixedPrincipalAmount() { - return this.currentPeriodFixedPrincipalAmount; - } - - public void setCurrentPeriodFixedPrincipalAmount( - BigDecimal currentPeriodFixedPrincipalAmount) { - this.currentPeriodFixedPrincipalAmount = currentPeriodFixedPrincipalAmount; - } - - public Integer fetchNumberOfRepaymentsAfterExceptions() { - return this.actualNumberOfRepayments; - } - - public LocalDate getSeedDate() { - return this.seedDate; - } - - public CalendarHistoryDataWrapper getCalendarHistoryDataWrapper() { - return this.calendarHistoryDataWrapper; - } - - public Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled() { - return this.isInterestChargedFromDateSameAsDisbursalDateEnabled; - } - - public Integer getNumberOfdays() { - return numberOfDays; - } - - public boolean isSkipRepaymentOnFirstDayofMonth() { - return isSkipRepaymentOnFirstDayOfMonth; - } + /** + * Integer representing the number of 'repayment frequencies' or + * installments where 'grace' should apply to the charging of interest in a + * loans repayment period (installment). + * + * Note: The loan is interest-free for the period of time + * indicated. + */ + private final Integer interestChargingGrace; + /** + * Legacy method of support 'grace' on the charging of interest on a loan. + * + *

+ * For the typical structured loan, its reasonable to use an integer to + * indicate the number of 'repayment frequency' periods the 'grace' should + * apply to but for slightly irregular loans where the period between + * disbursement and the date of the 'first repayment period' isnt doest + * match the 'repayment frequency' but can be less (15days instead of 1 + * month) or more (6 weeks instead of 1 month) - The idea was to use a date + * to indicate from whence interest should be charged. + *

+ */ + private LocalDate interestChargedFromDate; + private final Money inArrearsTolerance; + + private final Integer graceOnArrearsAgeing; + + // added + private LocalDate loanEndDate; + + private final List disbursementDatas; + + private final boolean multiDisburseLoan; + + private BigDecimal fixedEmiAmount; + + private BigDecimal fixedPrincipalAmount; + + private BigDecimal currentPeriodFixedEmiAmount; + + private BigDecimal currentPeriodFixedPrincipalAmount; + + private final BigDecimal actualFixedEmiAmount; + + private final BigDecimal maxOutstandingBalance; + + private Money totalInterestDue; + + private final DaysInMonthType daysInMonthType; + + private final DaysInYearType daysInYearType; + + private final boolean interestRecalculationEnabled; + + private final LoanRescheduleStrategyMethod rescheduleStrategyMethod; + + private final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod; + + private final CalendarInstance restCalendarInstance; + + private final RecalculationFrequencyType recalculationFrequencyType; + + private final CalendarInstance compoundingCalendarInstance; + + private final RecalculationFrequencyType compoundingFrequencyType; + private final boolean allowCompoundingOnEod; + + private final BigDecimal principalThresholdForLastInstalment; + private final Integer installmentAmountInMultiplesOf; + + private final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy; + + private Money approvedPrincipal = null; + + private final LoanTermVariationsDataWrapper variationsDataWrapper; + + private Money adjustPrincipalForFlatLoans; + + private final LocalDate seedDate; + + private final CalendarHistoryDataWrapper calendarHistoryDataWrapper; + + private final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled; + + private final Integer numberOfDays; + + private final boolean isSkipRepaymentOnFirstDayOfMonth; + + private final HolidayDetailDTO holidayDetailDTO; + + public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, + final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType, + final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod, + final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate, + final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion, + final Money principalMoney, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, + final LocalDate calculatedRepaymentsStartingFromDate, final Integer graceOnPrincipalPayment, + final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, + final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final Money inArrearsTolerance, + final boolean multiDisburseLoan, final BigDecimal emiAmount, final List disbursementDatas, + final BigDecimal maxOutstandingBalance, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, + final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled, + final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, + final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod, + final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, + final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, + final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, + BigDecimal approvedAmount, List loanTermVariations, + Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, final Integer numberOfdays, + boolean isSkipRepaymentOnFirstDayofMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod) { + + final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, + repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, + interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, + allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, + calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, + interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, + graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, + interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, + compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, + preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, + isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth, holidayDetailDTO, + allowCompoundingOnEod); + + } + + public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek, + final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, + final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance, + final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount, + final List disbursementDatas, final BigDecimal maxOutstandingBalance, + final LocalDate interestChargedFromDate, final BigDecimal principalThresholdForLastInstalment, + final Integer installmentAmountInMultiplesOf, final RecalculationFrequencyType recalculationFrequencyType, + final CalendarInstance restCalendarInstance, final InterestRecalculationCompoundingMethod compoundingMethod, + final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, + final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate, + List loanTermVariations, final Integer numberOfdays, final boolean isSkipRepaymentOnFirstDayofMonth, + final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod) { + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + + return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek, + expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsTolerance, + loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, interestChargedFromDate, + principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance, + compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, loanPreClosureInterestCalculationStrategy, + rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations, + calendarHistoryDataWrapper, numberOfdays, isSkipRepaymentOnFirstDayofMonth, holidayDetailDTO, allowCompoundingOnEod); + } + + public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek, + final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, + final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance, + final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount, + final List disbursementDatas, final BigDecimal maxOutstandingBalance, + final LocalDate interestChargedFromDate, final BigDecimal principalThresholdForLastInstalment, + final Integer installmentAmountInMultiplesOf, final RecalculationFrequencyType recalculationFrequencyType, + final CalendarInstance restCalendarInstance, final InterestRecalculationCompoundingMethod compoundingMethod, + final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, + final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount, + BigDecimal annualNominalInterestRate, final List loanTermVariations, + final CalendarHistoryDataWrapper calendarHistoryDataWrapper, final Integer numberOfdays, + final boolean isSkipRepaymentOnFirstDayofMonth, final HolidayDetailDTO holidayDetailDTO, final boolean allowCompoundingOnEod) { + + final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); + final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); + final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); + final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod(); + final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); + final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); + final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail + .getInterestCalculationPeriodMethod(); + final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion(); + final Money principalMoney = loanProductRelatedDetail.getPrincipal(); + + // + final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment(); + final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail.recurringMoratoriumOnPrincipalPeriods(); + final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment(); + final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged(); + + // Interest recalculation settings + final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); + final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); + final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); + final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; + return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, + repaymentEvery, repaymentPeriodFrequencyType, ((nthDay != null) ? nthDay.getValue() : null), dayOfWeek, amortizationMethod, + interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, + interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, + repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, + graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, + maxOutstandingBalance, loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, + isInterestRecalculationEnabled, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, + recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, + installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, + loanTermVariations, calendarHistoryDataWrapper, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, + isSkipRepaymentOnFirstDayofMonth, holidayDetailDTO, allowCompoundingOnEod); + } + + public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, final LocalDate expectedDisbursementDate, + final LocalDate repaymentsStartingFromDate, final LocalDate calculatedRepaymentsStartingFromDate, + final Money inArrearsTolerance, final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, + final BigDecimal emiAmount, final List disbursementDatas, final BigDecimal maxOutstandingBalance, + final LocalDate interestChargedFromDate, final LoanInterestRecalculationDetails interestRecalculationDetails, + final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType, + final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, + final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, final Calendar loanCalendar, + BigDecimal approvedAmount, final BigDecimal annualNominalInterestRate, final List loanTermVariations, + Integer numberOfdays, boolean isSkipRepaymentOnFirstDayofMonth, final HolidayDetailDTO holidayDetailDTO, + final boolean allowCompoundingOnEod) { + + final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); + final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); + final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); + final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod(); + final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); + final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); + final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail + .getInterestCalculationPeriodMethod(); + final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion(); + final Money principalMoney = loanProductRelatedDetail.getPrincipal(); + + // + final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment(); + final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail.recurringMoratoriumOnPrincipalPeriods(); + final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment(); + final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged(); + + // Interest recalculation settings + final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); + final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); + final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); + LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; + if (isInterestRecalculationEnabled) { + rescheduleStrategyMethod = interestRecalculationDetails.getRescheduleStrategyMethod(); + interestRecalculationCompoundingMethod = interestRecalculationDetails.getInterestRecalculationCompoundingMethod(); + } + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; + + return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, + repaymentEvery, repaymentPeriodFrequencyType, null, null, amortizationMethod, interestMethod, interestRatePerPeriod, + interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, + allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, + calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, + interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, + loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, + rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, + compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, + loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, + isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth, holidayDetailDTO, + allowCompoundingOnEod); + + } + + public static LoanApplicationTerms assembleFrom(final LoanApplicationTerms applicationTerms, + final List loanTermVariations) { + return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency, + applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, applicationTerms.repaymentEvery, + applicationTerms.repaymentPeriodFrequencyType, applicationTerms.nthDay, applicationTerms.weekDayType, + applicationTerms.amortizationMethod, applicationTerms.interestMethod, applicationTerms.interestRatePerPeriod, + applicationTerms.interestRatePeriodFrequencyType, applicationTerms.annualNominalInterestRate, + applicationTerms.interestCalculationPeriodMethod, applicationTerms.allowPartialPeriodInterestCalcualtion, + applicationTerms.principal, applicationTerms.expectedDisbursementDate, applicationTerms.repaymentsStartingFromDate, + applicationTerms.calculatedRepaymentsStartingFromDate, applicationTerms.principalGrace, + applicationTerms.recurringMoratoriumOnPrincipalPeriods, applicationTerms.interestPaymentGrace, applicationTerms.interestChargingGrace, applicationTerms.interestChargedFromDate, + applicationTerms.inArrearsTolerance, applicationTerms.multiDisburseLoan, applicationTerms.actualFixedEmiAmount, + applicationTerms.disbursementDatas, applicationTerms.maxOutstandingBalance, applicationTerms.graceOnArrearsAgeing, + applicationTerms.daysInMonthType, applicationTerms.daysInYearType, applicationTerms.interestRecalculationEnabled, + applicationTerms.rescheduleStrategyMethod, applicationTerms.interestRecalculationCompoundingMethod, + applicationTerms.restCalendarInstance, applicationTerms.recalculationFrequencyType, + applicationTerms.compoundingCalendarInstance, applicationTerms.compoundingFrequencyType, + applicationTerms.principalThresholdForLastInstalment, applicationTerms.installmentAmountInMultiplesOf, + applicationTerms.preClosureInterestCalculationStrategy, applicationTerms.loanCalendar, + applicationTerms.approvedPrincipal.getAmount(), loanTermVariations, applicationTerms.calendarHistoryDataWrapper, + applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled, applicationTerms.numberOfDays, + applicationTerms.isSkipRepaymentOnFirstDayOfMonth, applicationTerms.holidayDetailDTO, + applicationTerms.allowCompoundingOnEod); + } + + private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, + final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer nthDay, final DayOfWeekType weekDayType, + final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod, + final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate, + final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion, + final Money principal, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, + final LocalDate calculatedRepaymentsStartingFromDate, final Integer principalGrace, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer interestPaymentGrace, + final Integer interestChargingGrace, final LocalDate interestChargedFromDate, final Money inArrearsTolerance, + final boolean multiDisburseLoan, final BigDecimal emiAmount, final List disbursementDatas, + final BigDecimal maxOutstandingBalance, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, + final DaysInYearType daysInYearType, 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, final Calendar loanCalendar, + BigDecimal approvedAmount, List loanTermVariations, + final CalendarHistoryDataWrapper calendarHistoryDataWrapper, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, + final Integer numberOfdays, final boolean isSkipRepaymentOnFirstDayofMonth, final HolidayDetailDTO holidayDetailDTO, + final boolean allowCompoundingOnEod) { + + this.currency = currency; + this.loanTermFrequency = loanTermFrequency; + this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; + this.numberOfRepayments = numberOfRepayments; + this.repaymentEvery = repaymentEvery; + this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType; + this.nthDay = nthDay; + this.weekDayType = weekDayType; + this.amortizationMethod = amortizationMethod; + + this.interestMethod = interestMethod; + this.interestRatePerPeriod = interestRatePerPeriod; + this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType; + this.annualNominalInterestRate = annualNominalInterestRate; + this.interestCalculationPeriodMethod = interestCalculationPeriodMethod; + this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion; + + this.principal = principal; + this.expectedDisbursementDate = expectedDisbursementDate; + this.repaymentsStartingFromDate = repaymentsStartingFromDate; + this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; + + this.principalGrace = principalGrace; + this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods; + this.interestPaymentGrace = interestPaymentGrace; + this.interestChargingGrace = interestChargingGrace; + this.interestChargedFromDate = interestChargedFromDate; + + this.inArrearsTolerance = inArrearsTolerance; + this.multiDisburseLoan = multiDisburseLoan; + this.fixedEmiAmount = emiAmount; + this.actualFixedEmiAmount = emiAmount; + this.disbursementDatas = disbursementDatas; + this.maxOutstandingBalance = maxOutstandingBalance; + this.graceOnArrearsAgeing = graceOnArrearsAgeing; + this.daysInMonthType = daysInMonthType; + this.daysInYearType = daysInYearType; + this.interestRecalculationEnabled = isInterestRecalculationEnabled; + 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; + this.isSkipRepaymentOnFirstDayOfMonth = isSkipRepaymentOnFirstDayofMonth; + this.numberOfDays = numberOfdays; + + this.loanCalendar = loanCalendar; + this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount); + this.variationsDataWrapper = new LoanTermVariationsDataWrapper(loanTermVariations); + this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments(); + this.adjustPrincipalForFlatLoans = principal.zero(); + if (this.calculatedRepaymentsStartingFromDate == null) { + this.seedDate = this.expectedDisbursementDate; + } else { + this.seedDate = this.calculatedRepaymentsStartingFromDate; + } + this.calendarHistoryDataWrapper = calendarHistoryDataWrapper; + this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled; + this.holidayDetailDTO = holidayDetailDTO; + this.allowCompoundingOnEod = allowCompoundingOnEod; + } + + public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, + final int periodNumber) { + + Money adjusted = principalForPeriod; + + final Money totalPrincipalRemaining = this.principal.minus(totalCumulativePrincipalToDate); + if (totalPrincipalRemaining.isLessThanZero()) { + // paid too much principal, subtract amount that overpays from + // principal paid for period. + adjusted = principalForPeriod.minus(totalPrincipalRemaining.abs()); + } else if (this.actualFixedEmiAmount != null) { + final Money difference = this.principal.minus(totalCumulativePrincipalToDate); + final Money principalThreshold = principalForPeriod.multipliedBy(this.principalThresholdForLastInstalment).dividedBy(100, + MoneyHelper.getRoundingMode()); + if (difference.isLessThan(principalThreshold)) { + adjusted = principalForPeriod.plus(difference.abs()); + } + } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, periodNumber)) { + + final Money difference = totalCumulativePrincipalToDate.minus(this.principal); + if (difference.isLessThanZero()) { + adjusted = principalForPeriod.plus(difference.abs()); + } else if (difference.isGreaterThanZero()) { + adjusted = principalForPeriod.minus(difference.abs()); + } + } + + return adjusted; + } + + public Money adjustInterestIfLastRepaymentPeriod(final Money interestForThisPeriod, final Money totalCumulativeInterestToDate, + final Money totalInterestDueForLoan, final int periodNumber) { + + Money adjusted = interestForThisPeriod; + + final Money totalInterestRemaining = totalInterestDueForLoan.minus(totalCumulativeInterestToDate); + if (totalInterestRemaining.isLessThanZero()) { + // paid too much interest, subtract amount that overpays from + // interest paid for period. + adjusted = interestForThisPeriod.minus(totalInterestRemaining.abs()); + } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, periodNumber)) { + final Money interestDifference = totalCumulativeInterestToDate.minus(totalInterestDueForLoan); + if (interestDifference.isLessThanZero()) { + adjusted = interestForThisPeriod.plus(interestDifference.abs()); + } else if (interestDifference.isGreaterThanZero()) { + adjusted = interestForThisPeriod.minus(interestDifference.abs()); + } + } + if (adjusted.isLessThanZero()) { + adjusted = adjusted.plus(adjusted); + } + return adjusted; + } + + /** + * Calculates the total interest to be charged on loan taking into account + * grace settings. + * + */ + public Money calculateTotalInterestCharged(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { + + Money totalInterestCharged = this.principal.zero(); + + switch (this.interestMethod) { + case FLAT: + final Money totalInterestChargedForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc); + + final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); + + final Money totalGraceOnInterestCharged = totalInterestPerInstallment.multiplyRetainScale(getInterestChargingGrace(), + mc.getRoundingMode()); + + totalInterestCharged = totalInterestChargedForLoanTerm.minus(totalGraceOnInterestCharged); + break; + case DECLINING_BALANCE: + case INVALID: + break; + } + + return totalInterestCharged; + } + + public Money calculateTotalPrincipalForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final Money outstandingBalance, + final int periodNumber, final MathContext mc, Money interestForThisInstallment) { + + Money principalForInstallment = this.principal.zero(); + + switch (this.interestMethod) { + case FLAT: + principalForInstallment = calculateTotalPrincipalPerPeriodWithoutGrace(mc, periodNumber); + break; + case DECLINING_BALANCE: + switch (this.amortizationMethod) { + case EQUAL_INSTALLMENTS: + Money totalPmtForThisInstallment = pmtForInstallment(calculator, outstandingBalance, periodNumber, mc); + principalForInstallment = calculatePrincipalDueForInstallment(periodNumber, totalPmtForThisInstallment, + interestForThisInstallment); + break; + case EQUAL_PRINCIPAL: + principalForInstallment = calculateEqualPrincipalDueForInstallment(mc, periodNumber); + break; + case INVALID: + break; + } + break; + case INVALID: + break; + } + + return principalForInstallment; + } + + public Money pmtForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final Money outstandingBalance, + final int periodNumber, final MathContext mc) { + // Calculate exact period from disbursement date + final LocalDate periodStartDate = getExpectedDisbursementDate().withDayOfMonth(1); + final LocalDate periodEndDate = getPeriodEndDate(periodStartDate); + // equal installments + final int periodsElapsed = periodNumber - 1; + // with periodic interest for default month and year for + // equal installment + final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc, DaysInMonthType.DAYS_30, + DaysInYearType.DAYS_365, periodStartDate, periodEndDate); + Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(periodicInterestRateForRepaymentPeriod, + outstandingBalance, periodsElapsed); + return totalPmtForThisInstallment; + } + + private LocalDate getPeriodEndDate(final LocalDate startDate) { + LocalDate dueRepaymentPeriodDate = startDate; + switch (this.repaymentPeriodFrequencyType) { + case DAYS: + dueRepaymentPeriodDate = startDate.plusDays(this.repaymentEvery); + break; + case WEEKS: + dueRepaymentPeriodDate = startDate.plusWeeks(this.repaymentEvery); + break; + case MONTHS: + dueRepaymentPeriodDate = startDate.plusMonths(this.repaymentEvery); + break; + case YEARS: + dueRepaymentPeriodDate = startDate.plusYears(this.repaymentEvery); + break; + case INVALID: + break; + } + return dueRepaymentPeriodDate; + } + + public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator, + final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc, + final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LocalDate periodStartDate, + final LocalDate periodEndDate) { + + Money interestForInstallment = this.principal.zero(); + Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy(); + + switch (this.interestMethod) { + case FLAT: + + switch (this.amortizationMethod) { + case EQUAL_INSTALLMENTS: + // average out outstanding interest over remaining + // instalments where interest is applicable + interestForInstallment = calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(calculator, periodNumber, + mc); + break; + case EQUAL_PRINCIPAL: + // interest follows time-value of money and is brought + // forward to next applicable interest payment period + final PrincipalInterest result = calculateTotalFlatInterestForPeriod(calculator, periodNumber, mc, + interestBroughtForwardDueToGrace); + interestForInstallment = result.interest(); + interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace(); + break; + case INVALID: + break; + } + break; + case DECLINING_BALANCE: + + final Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace( + calculator, mc, outstandingBalance, periodStartDate, periodEndDate); + + final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace( + calculator, interestCalculationGraceOnRepaymentPeriodFraction, mc, outstandingBalance, periodNumber, + periodStartDate, periodEndDate); + + interestForInstallment = interestForThisInstallmentAfterGrace; + if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) { + interestForInstallment = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentAfterGrace); + interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero(); + } else if (isInterestFreeGracePeriod(periodNumber)) { + interestForInstallment = interestForInstallment.zero(); + } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { + interestForInstallment = interestForThisInstallmentAfterGrace; + } else { + interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace); + } + break; + case INVALID: + break; + } + + return new PrincipalInterest(null, interestForInstallment, interestBroughtForwardDueToGrace); + } + + private final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) { + return periodNumber == numberOfRepayments; + } + + /** + * general method to calculate totalInterestDue discounting any grace + * settings + */ + private Money calculateTotalFlatInterestDueWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { + + Money totalInterestDue = this.principal.zero(); + + switch (this.interestMethod) { + case FLAT: + final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc); + totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode()); + + break; + case DECLINING_BALANCE: + break; + case INVALID: + break; + } + + if (this.totalInterestDue != null) { + totalInterestDue = this.totalInterestDue; + } + + return totalInterestDue; + } + + private BigDecimal calculateFlatInterestRateForLoanTerm(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { + + final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); + + final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); + final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); + + final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm(); + + return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc) + .multiply(loanTermFrequencyBigDecimal); + } + + private BigDecimal calculatePeriodsInLoanTerm() { + + BigDecimal periodsInLoanTerm = BigDecimal.valueOf(this.loanTermFrequency); + switch (this.interestCalculationPeriodMethod) { + case DAILY: + // number of days from 'ideal disbursement' to final date + + LocalDate loanStartDate = getExpectedDisbursementDate(); + if (getInterestChargedFromDate() != null && loanStartDate.isBefore(getInterestChargedFromLocalDate())) { + loanStartDate = getInterestChargedFromLocalDate(); + } + + final int periodsInLoanTermInteger = Days.daysBetween(loanStartDate, this.loanEndDate).getDays(); + periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger); + break; + case INVALID: + break; + case SAME_AS_REPAYMENT_PERIOD: + if (this.allowPartialPeriodInterestCalcualtion) { + LocalDate startDate = getExpectedDisbursementDate(); + if (getInterestChargedFromDate() != null) { + startDate = getInterestChargedFromLocalDate(); + } + periodsInLoanTerm = calculatePeriodsBetweenDates(startDate, this.loanEndDate); + } + break; + } + + return periodsInLoanTerm; + } + + public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final LocalDate endDate) { + BigDecimal numberOfPeriods = BigDecimal.ZERO; + switch (this.repaymentPeriodFrequencyType) { + case DAYS: + int numOfDays = Days.daysBetween(startDate, endDate).getDays(); + numberOfPeriods = BigDecimal.valueOf((double) numOfDays); + break; + case WEEKS: + int numberOfWeeks = Weeks.weeksBetween(startDate, endDate).getWeeks(); + int daysLeftAfterWeeks = Days.daysBetween(startDate.plusWeeks(numberOfWeeks), endDate).getDays(); + numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfWeeks)).add( + BigDecimal.valueOf((double) daysLeftAfterWeeks / 7)); + break; + case MONTHS: + int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths(); + LocalDate startDateAfterConsideringMonths = null; + LocalDate endDateAfterConsideringMonths = null; + int diffDays = 0; + if (this.loanCalendar == null) { + startDateAfterConsideringMonths = startDate.plusMonths(numberOfMonths); + startDateAfterConsideringMonths = CalendarUtils.adjustDate(startDateAfterConsideringMonths, getSeedDate(), + this.repaymentPeriodFrequencyType); + endDateAfterConsideringMonths = startDate.plusMonths(numberOfMonths + 1); + endDateAfterConsideringMonths = CalendarUtils.adjustDate(endDateAfterConsideringMonths, getSeedDate(), + this.repaymentPeriodFrequencyType); + } else { + LocalDate expectedStartDate = startDate; + if (!CalendarUtils.isValidRedurringDate(loanCalendar.getRecurrence(), + loanCalendar.getStartDateLocalDate().minusMonths(getRepaymentEvery()), startDate)) { + expectedStartDate = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(), + startDate.minusMonths(getRepaymentEvery()), startDate.minusMonths(getRepaymentEvery()), + getRepaymentEvery(), + CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), + this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); + } + if (!expectedStartDate.isEqual(startDate)) { + diffDays = Days.daysBetween(startDate, expectedStartDate).getDays(); + } + if (numberOfMonths == 0) { + startDateAfterConsideringMonths = expectedStartDate; + } else { + startDateAfterConsideringMonths = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(), + expectedStartDate, expectedStartDate.plusMonths(numberOfMonths), getRepaymentEvery(), + CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), + this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); + } + endDateAfterConsideringMonths = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(), + startDateAfterConsideringMonths, startDateAfterConsideringMonths.plusDays(1), getRepaymentEvery(), + CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), + this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); + } + int daysLeftAfterMonths = Days.daysBetween(startDateAfterConsideringMonths, endDate).getDays() + diffDays; + int daysInPeriodAfterMonths = Days.daysBetween(startDateAfterConsideringMonths, endDateAfterConsideringMonths).getDays(); + numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfMonths)).add( + BigDecimal.valueOf((double) daysLeftAfterMonths / daysInPeriodAfterMonths)); + break; + case YEARS: + int numberOfYears = Years.yearsBetween(startDate, endDate).getYears(); + LocalDate startDateAfterConsideringYears = startDate.plusYears(numberOfYears); + LocalDate endDateAfterConsideringYears = startDate.plusYears(numberOfYears + 1); + int daysLeftAfterYears = Days.daysBetween(startDateAfterConsideringYears, endDate).getDays(); + int daysInPeriodAfterYears = Days.daysBetween(startDateAfterConsideringYears, endDateAfterConsideringYears).getDays(); + numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfYears)).add( + BigDecimal.valueOf((double) daysLeftAfterYears / daysInPeriodAfterYears)); + break; + default: + break; + } + return numberOfPeriods; + } + + public void updateLoanEndDate(final LocalDate loanEndDate) { + this.loanEndDate = loanEndDate; + } + + private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { + + final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc); + + return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments), mc.getRoundingMode()); + } + + private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc, final int periodNumber) { + final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment(); + Money principalPerPeriod = this.principal.dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus( + this.adjustPrincipalForFlatLoans); + if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { + principalPerPeriod = principalPerPeriod.zero(); + } + if (!isPrincipalGraceApplicableForThisPeriod(periodNumber) && currentPeriodFixedPrincipalAmount != null) { + this.adjustPrincipalForFlatLoans = this.adjustPrincipalForFlatLoans.plus(principalPerPeriod.minus( + currentPeriodFixedPrincipalAmount).dividedBy(this.actualNumberOfRepayments - periodNumber, mc.getRoundingMode())); + principalPerPeriod = this.principal.zero().plus(currentPeriodFixedPrincipalAmount); + + } + return principalPerPeriod; + } + + private PrincipalInterest calculateTotalFlatInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator, + final int periodNumber, final MathContext mc, final Money cumulatingInterestPaymentDueToGrace) { + + Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy(); + + Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); + if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { + interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForInstallment); + interestForInstallment = interestForInstallment.zero(); + } else if (isInterestFreeGracePeriod(periodNumber)) { + interestForInstallment = interestForInstallment.zero(); + } else if (isFirstPeriodAfterInterestPaymentGracePeriod(periodNumber)) { + interestForInstallment = cumulatingInterestPaymentDueToGrace.plus(interestForInstallment); + interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero(); + } + + return new PrincipalInterest(null, interestForInstallment, interestBroughtForwardDueToGrace); + } + + /* + * calculates the interest that should be due for a given scheduled loan + * repayment period. It takes into account GRACE periods and calculates how + * much interest is due per period by averaging the number of periods where + * interest is due and should be paid against the total known interest that + * is due without grace. + */ + private Money calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(final PaymentPeriodsInOneYearCalculator calculator, + final int periodNumber, final MathContext mc) { + + Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); + if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { + interestForInstallment = interestForInstallment.zero(); + } else if (isInterestFreeGracePeriod(periodNumber)) { + interestForInstallment = interestForInstallment.zero(); + } else { + + final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc); + + final Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); + + final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace()); + final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree); + + final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments); + + interestForInstallment = realTotalInterestForLoan + .dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode()); + } + + return interestForInstallment; + } + + private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, + final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, LocalDate periodStartDate, LocalDate periodEndDate) { + + final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); + + final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); + final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); + + BigDecimal periodicInterestRate = BigDecimal.ZERO; + BigDecimal loanTermFrequencyBigDecimal = calculateLoanTermFrequency(periodStartDate, periodEndDate); + switch (this.interestCalculationPeriodMethod) { + case INVALID: + break; + case DAILY: + // For daily work out number of days in the period + BigDecimal numberOfDaysInPeriod = BigDecimal.valueOf(Days.daysBetween(periodStartDate, periodEndDate).getDays()); + + final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc) + .divide(divisor, mc); + + switch (this.repaymentPeriodFrequencyType) { + case INVALID: + break; + case DAYS: + periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); + break; + case WEEKS: + periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); + break; + case MONTHS: + if (daysInMonthType.isDaysInMonth_30()) { + numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(30), mc); + } + periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); + break; + case YEARS: + switch (daysInYearType) { + case DAYS_360: + numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(360), mc); + break; + case DAYS_364: + numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(364), mc); + break; + case DAYS_365: + numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(365), mc); + break; + default: + break; + } + periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); + break; + } + break; + case SAME_AS_REPAYMENT_PERIOD: + periodicInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc) + .multiply(loanTermFrequencyBigDecimal); + break; + } + + return periodicInterestRate; + } + + private BigDecimal calculateLoanTermFrequency(final LocalDate periodStartDate, final LocalDate periodEndDate) { + BigDecimal loanTermFrequencyBigDecimal = BigDecimal.valueOf(this.repaymentEvery); + if (this.interestCalculationPeriodMethod.isDaily() || this.allowPartialPeriodInterestCalcualtion) { + loanTermFrequencyBigDecimal = calculatePeriodsBetweenDates(periodStartDate, periodEndDate); + } + return loanTermFrequencyBigDecimal; + } + + public BigDecimal interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, + final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) { + + long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue(); + int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays(); + if (isFallingInRepaymentPeriod(fromDate, toDate)) { + loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); + repaymentEvery = getPeriodsBetween(fromDate, toDate); + } + + final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); + final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); + final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide( + divisor, mc); + BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(repaymentEvery), mc); + return outstandingBalance.getAmount().multiply(interestRate, mc); + } + + private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) { + + // check if daysInYears is set if so change periodsInOneYear to days set + // in db + long periodsInOneYear; + boolean daysInYearToUse = (this.repaymentPeriodFrequencyType.getCode().equalsIgnoreCase("periodFrequencyType.days") && !this.daysInYearType + .getCode().equalsIgnoreCase("DaysInYearType.actual")); + if (daysInYearToUse) { + periodsInOneYear = this.daysInYearType.getValue().longValue(); + } else { + periodsInOneYear = calculator.calculate(this.repaymentPeriodFrequencyType).longValue(); + } + switch (this.interestCalculationPeriodMethod) { + case DAILY: + periodsInOneYear = (!this.daysInYearType.getCode().equalsIgnoreCase("DaysInYearType.actual")) ? this.daysInYearType + .getValue().longValue() : calculator.calculate(PeriodFrequencyType.DAYS).longValue(); + break; + case INVALID: + break; + case SAME_AS_REPAYMENT_PERIOD: + break; + } + + return periodsInOneYear; + } + + private int calculateNumberOfRepaymentsWithPrincipalPayment() { + int numPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, + this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), 0); + return numPeriods; + } + + private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(final Integer totalNumberOfRepaymentPeriods) { + return totalNumberOfRepaymentPeriods - Math.max(getInterestChargingGrace(), getInterestPaymentGrace()); + } + + public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) { + if (this.getRecurringMoratoriumOnPrincipalPeriods() > 0) { + return ((periodNumber > 0 && periodNumber <= getPrincipalGrace()) || (periodNumber > 0 && (((periodNumber - getPrincipalGrace()) % (this + .getRecurringMoratoriumOnPrincipalPeriods() + 1)) != 1))); + } + return periodNumber > 0 && periodNumber <= getPrincipalGrace(); + } + + private boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) { + return periodNumber > 0 && periodNumber <= getInterestPaymentGrace(); + } + + private boolean isFirstPeriodAfterInterestPaymentGracePeriod(final int periodNumber) { + return periodNumber > 0 && periodNumber == getInterestPaymentGrace() + 1; + } + + private boolean isInterestFreeGracePeriod(final int periodNumber) { + return periodNumber > 0 && periodNumber <= getInterestChargingGrace(); + } + + public Integer getPrincipalGrace() { + Integer graceOnPrincipalPayments = Integer.valueOf(0); + if (this.principalGrace != null) { + graceOnPrincipalPayments = this.principalGrace; + } + return graceOnPrincipalPayments; + } + + public Integer getRecurringMoratoriumOnPrincipalPeriods() { + Integer recurringMoratoriumOnPrincipalPeriods = Integer.valueOf(0); + if (this.recurringMoratoriumOnPrincipalPeriods != null) { + recurringMoratoriumOnPrincipalPeriods = this.recurringMoratoriumOnPrincipalPeriods; + } + return recurringMoratoriumOnPrincipalPeriods; + } + + public Integer getInterestPaymentGrace() { + Integer graceOnInterestPayments = Integer.valueOf(0); + if (this.interestPaymentGrace != null) { + graceOnInterestPayments = this.interestPaymentGrace; + } + return graceOnInterestPayments; + } + + public Integer getInterestChargingGrace() { + Integer graceOnInterestCharged = Integer.valueOf(0); + if (this.interestChargingGrace != null) { + graceOnInterestCharged = this.interestChargingGrace; + } + return graceOnInterestCharged; + } + + private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) { + + if (getFixedEmiAmount() == null) { + final double futureValue = 0; + final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue(); + + final Integer periodsRemaining = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, + this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodsElapsed); + + double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(), + principalDouble, futureValue, false); + + if (this.installmentAmountInMultiplesOf != null) { + installmentAmount = Money.roundToMultiplesOf(installmentAmount, this.installmentAmountInMultiplesOf); + } + setFixedEmiAmount(BigDecimal.valueOf(installmentAmount)); + } + return getFixedEmiAmount().doubleValue(); + } + + private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator, + final MathContext mc, final Money outstandingBalance, LocalDate periodStartDate, LocalDate periodEndDate) { + + Money interestDue = Money.zero(outstandingBalance.getCurrency()); + + final BigDecimal periodicInterestRate = periodicInterestRate(calculator, mc, this.daysInMonthType, this.daysInYearType, + periodStartDate, periodEndDate); + interestDue = outstandingBalance.multiplyRetainScale(periodicInterestRate, mc.getRoundingMode()); + + return interestDue; + } + + private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator, + final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance, + final int periodNumber, LocalDate periodStartDate, LocalDate periodEndDate) { + + Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance, + periodStartDate, periodEndDate); + + if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { + interest = interest.zero(); + } + + Double fraction = interestCalculationGraceOnRepaymentPeriodFraction; + + if (isInterestFreeGracePeriod(periodNumber)) { + interest = interest.zero(); + } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { + + if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) { + interest = interest.zero(); + fraction = fraction - Integer.valueOf(1).doubleValue(); + + } else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25") + && interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) { + + final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction); + interest = interest.minus(graceOnInterestForRepaymentPeriod); + fraction = Double.valueOf("0"); + } + } + + return interest; + } + + private boolean isInterestFreeGracePeriodFromDate(final double interestCalculationGraceOnRepaymentPeriodFraction) { + return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.0"); + } + + private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) { + Money principal = this.principal; + if (this.fixedPrincipalAmount == null) { + final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( + this.actualNumberOfRepayments, this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodNumber); + principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); + this.fixedPrincipalAmount = principal.getAmount(); + } + principal = Money.of(getCurrency(), getFixedPrincipalAmount()); + + if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { + principal = principal.zero(); + } + return principal; + } + + public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) { + final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods(this.actualNumberOfRepayments, + this.getRecurringMoratoriumOnPrincipalPeriods(), this.getPrincipalGrace(), periodNumber - 1); + Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); + this.fixedPrincipalAmount = principal.getAmount(); + } + + private static Integer calculateNumberOfRemainingPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods, + final int recurringMoratoriumOnPrincipalPeriods, final int PrincipalGrace, int periodsElapsed) { + if (PrincipalGrace > periodsElapsed) { + periodsElapsed = PrincipalGrace; + } + Integer periodsRemaining = totalNumberOfRepaymentPeriods - periodsElapsed; + if (recurringMoratoriumOnPrincipalPeriods > 0) { + double periodsRemainingDouble = ((double) periodsRemaining / ((double) (recurringMoratoriumOnPrincipalPeriods + 1))); + periodsRemaining = (int) Math.ceil(periodsRemainingDouble); + } + return periodsRemaining; + } + + public void setFixedPrincipalAmount(BigDecimal fixedPrincipalAmount) { + this.fixedPrincipalAmount = fixedPrincipalAmount; + } + + private Money calculatePrincipalDueForInstallment(final int periodNumber, final Money totalDuePerInstallment, final Money periodInterest) { + + Money principal = totalDuePerInstallment.minus(periodInterest); + if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { + principal = principal.zero(); + } + return principal; + } + + private Money calculateTotalDueForEqualInstallmentRepaymentPeriod(final BigDecimal periodicInterestRate, final Money balance, + final int periodsElapsed) { + + final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, balance, periodsElapsed); + + return Money.of(balance.getCurrency(), BigDecimal.valueOf(paymentPerRepaymentPeriod)); + } + + public LoanProductRelatedDetail toLoanProductRelatedDetail() { + final MonetaryCurrency currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces(), + this.currency.getCurrencyInMultiplesOf()); + + return LoanProductRelatedDetail.createFrom(currency, this.principal.getAmount(), this.interestRatePerPeriod, + this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod, + this.interestCalculationPeriodMethod, this.allowPartialPeriodInterestCalcualtion, this.repaymentEvery, + this.repaymentPeriodFrequencyType, this.numberOfRepayments, this.principalGrace, this.recurringMoratoriumOnPrincipalPeriods, this.interestPaymentGrace, + this.interestChargingGrace, this.amortizationMethod, this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, + this.daysInMonthType.getValue(), this.daysInYearType.getValue(), this.interestRecalculationEnabled); + } + + public Integer getLoanTermFrequency() { + return this.loanTermFrequency; + } + + public PeriodFrequencyType getLoanTermPeriodFrequencyType() { + return this.loanTermPeriodFrequencyType; + } + + public Integer getRepaymentEvery() { + return this.repaymentEvery; + } + + public PeriodFrequencyType getRepaymentPeriodFrequencyType() { + return this.repaymentPeriodFrequencyType; + } + + public Date getRepaymentStartFromDate() { + Date dateValue = null; + if (this.repaymentsStartingFromDate != null) { + dateValue = this.repaymentsStartingFromDate.toDate(); + } + return dateValue; + } + + public Date getInterestChargedFromDate() { + Date dateValue = null; + if (this.interestChargedFromDate != null) { + dateValue = this.interestChargedFromDate.toDate(); + } + return dateValue; + } + + public void setPrincipal(Money principal) { + this.principal = principal; + } + + public LocalDate getInterestChargedFromLocalDate() { + return this.interestChargedFromDate; + } + + public InterestMethod getInterestMethod() { + return this.interestMethod; + } + + public AmortizationMethod getAmortizationMethod() { + return this.amortizationMethod; + } + + public MonetaryCurrency getCurrency() { + return this.principal.getCurrency(); + } + + public Integer getNumberOfRepayments() { + return this.numberOfRepayments; + } + + public LocalDate getExpectedDisbursementDate() { + return this.expectedDisbursementDate; + } + + public LocalDate getRepaymentsStartingFromLocalDate() { + return this.repaymentsStartingFromDate; + } + + public LocalDate getCalculatedRepaymentsStartingFromLocalDate() { + return this.calculatedRepaymentsStartingFromDate; + } + + public Money getPrincipal() { + return this.principal; + } + + public Money getApprovedPrincipal() { + return this.approvedPrincipal; + } + + public List getDisbursementDatas() { + return this.disbursementDatas; + } + + public boolean isMultiDisburseLoan() { + return this.multiDisburseLoan; + } + + public Money getMaxOutstandingBalance() { + return Money.of(getCurrency(), this.maxOutstandingBalance); + } + + public BigDecimal getFixedEmiAmount() { + BigDecimal fixedEmiAmount = this.fixedEmiAmount; + if (getCurrentPeriodFixedEmiAmount() != null) { + fixedEmiAmount = getCurrentPeriodFixedEmiAmount(); + } + return fixedEmiAmount; + } + + public Integer getNthDay() { + return this.nthDay; + } + + public DayOfWeekType getWeekDayType() { + return this.weekDayType; + } + + public void setFixedEmiAmount(BigDecimal fixedEmiAmount) { + this.fixedEmiAmount = fixedEmiAmount; + } + + public void resetFixedEmiAmount() { + this.fixedEmiAmount = this.actualFixedEmiAmount; + } + + public LoanRescheduleStrategyMethod getLoanRescheduleStrategyMethod() { + return LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT; + } + + public boolean isInterestRecalculationEnabled() { + return this.interestRecalculationEnabled; + } + + public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() { + return this.rescheduleStrategyMethod; + } + + public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() { + return this.interestRecalculationCompoundingMethod; + } + + public CalendarInstance getRestCalendarInstance() { + return this.restCalendarInstance; + } + + private boolean isFallingInRepaymentPeriod(LocalDate fromDate, LocalDate toDate) { + boolean isSameAsRepaymentPeriod = false; + if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) { + switch (this.repaymentPeriodFrequencyType) { + case WEEKS: + int days = Days.daysBetween(fromDate, toDate).getDays(); + isSameAsRepaymentPeriod = (days % 7) == 0; + break; + case MONTHS: + boolean isFromDateOnEndDate = false; + if (fromDate.getDayOfMonth() > fromDate.plusDays(1).getDayOfMonth()) { + isFromDateOnEndDate = true; + } + boolean isToDateOnEndDate = false; + if (toDate.getDayOfMonth() > toDate.plusDays(1).getDayOfMonth()) { + isToDateOnEndDate = true; + } + + if (isFromDateOnEndDate && isToDateOnEndDate) { + isSameAsRepaymentPeriod = true; + } else { + + int months = getPeriodsBetween(fromDate, toDate); + fromDate = fromDate.plusMonths(months); + isSameAsRepaymentPeriod = fromDate.isEqual(toDate); + } + + break; + default: + break; + } + } + return isSameAsRepaymentPeriod; + } + + private Integer getPeriodsBetween(LocalDate fromDate, LocalDate toDate) { + Integer numberOfPeriods = 0; + PeriodType periodType = PeriodType.yearMonthDay(); + Period difference = new Period(fromDate, toDate, periodType); + switch (this.repaymentPeriodFrequencyType) { + case DAYS: + numberOfPeriods = difference.getDays(); + break; + case WEEKS: + periodType = PeriodType.weeks(); + difference = new Period(fromDate, toDate, periodType); + numberOfPeriods = difference.getWeeks(); + break; + case MONTHS: + numberOfPeriods = difference.getMonths(); + break; + case YEARS: + numberOfPeriods = difference.getYears(); + break; + default: + break; + } + return numberOfPeriods; + } + + public RecalculationFrequencyType getRecalculationFrequencyType() { + return this.recalculationFrequencyType; + } + + public void updateNumberOfRepayments(final Integer numberOfRepayments) { + this.numberOfRepayments = numberOfRepayments; + this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments(); + + } + + public void updatePrincipalGrace(final Integer principalGrace) { + this.principalGrace = principalGrace; + } + + public void updateInterestPaymentGrace(final Integer interestPaymentGrace) { + this.interestPaymentGrace = interestPaymentGrace; + } + + public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) { + if (interestRatePerPeriod != null) { + this.interestRatePerPeriod = interestRatePerPeriod; + } + } + + public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) { + if (annualNominalInterestRate != null) { + this.annualNominalInterestRate = annualNominalInterestRate; + } + } + + public BigDecimal getAnnualNominalInterestRate() { + return this.annualNominalInterestRate; + } + + public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) { + if (interestChargedFromDate != null) { + this.interestChargedFromDate = interestChargedFromDate; + } + } + + public void updateLoanTermFrequency(Integer loanTermFrequency) { + if (loanTermFrequency != null) { + this.loanTermFrequency = loanTermFrequency; + } + } + + public void updateTotalInterestDue(Money totalInterestDue) { + + if (totalInterestDue != null) { + this.totalInterestDue = totalInterestDue; + } + } + + public ApplicationCurrency getApplicationCurrency() { + return this.currency; + } + + public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { + return this.interestCalculationPeriodMethod; + } + + public LoanPreClosureInterestCalculationStrategy getPreClosureInterestCalculationStrategy() { + return this.preClosureInterestCalculationStrategy; + } + + public CalendarInstance getCompoundingCalendarInstance() { + return this.compoundingCalendarInstance; + } + + public RecalculationFrequencyType getCompoundingFrequencyType() { + return this.compoundingFrequencyType; + } + + public BigDecimal getActualFixedEmiAmount() { + return this.actualFixedEmiAmount; + } + + public Calendar getLoanCalendar() { + return loanCalendar; + } + + public BigDecimal getFixedPrincipalAmount() { + BigDecimal fixedPrincipalAmount = this.fixedPrincipalAmount; + if (getCurrentPeriodFixedPrincipalAmount() != null) { + fixedPrincipalAmount = getCurrentPeriodFixedPrincipalAmount(); + } + return fixedPrincipalAmount; + } + + public LoanTermVariationsDataWrapper getLoanTermVariations() { + return this.variationsDataWrapper; + } + + public BigDecimal getCurrentPeriodFixedEmiAmount() { + return this.currentPeriodFixedEmiAmount; + } + + public void setCurrentPeriodFixedEmiAmount(BigDecimal currentPeriodFixedEmiAmount) { + this.currentPeriodFixedEmiAmount = currentPeriodFixedEmiAmount; + } + + public BigDecimal getCurrentPeriodFixedPrincipalAmount() { + return this.currentPeriodFixedPrincipalAmount; + } + + public void setCurrentPeriodFixedPrincipalAmount(BigDecimal currentPeriodFixedPrincipalAmount) { + this.currentPeriodFixedPrincipalAmount = currentPeriodFixedPrincipalAmount; + } + + public Integer fetchNumberOfRepaymentsAfterExceptions() { + return this.actualNumberOfRepayments; + } + + public LocalDate getSeedDate() { + return this.seedDate; + } + + public CalendarHistoryDataWrapper getCalendarHistoryDataWrapper() { + return this.calendarHistoryDataWrapper; + } + + public Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled() { + return this.isInterestChargedFromDateSameAsDisbursalDateEnabled; + } + + public Integer getNumberOfdays() { + return numberOfDays; + } + + public boolean isSkipRepaymentOnFirstDayofMonth() { + return isSkipRepaymentOnFirstDayOfMonth; + } + + public HolidayDetailDTO getHolidayDetailDTO() { + return this.holidayDetailDTO; + } + + public boolean allowCompoundingOnEod() { + return this.allowCompoundingOnEod; + } + + public Money getTotalDisbursedAmount() { + Money disbursedAmount = Money.zero(getCurrency()); + if (isMultiDisburseLoan()) { + for (DisbursementData disbursement : getDisbursementDatas()) { + if (disbursement.isDisbursed()) { + disbursedAmount = disbursedAmount.plus(disbursement.amount()); + } + } + } else { + disbursedAmount = getPrincipal(); + } + return disbursedAmount; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java index acb33a8bc..87d2332ae 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java @@ -19,8 +19,10 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; +import java.util.List; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.joda.time.LocalDate; @@ -122,4 +124,8 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo public void addInterestAmount(@SuppressWarnings("unused") Money principalDue) { return; } + @Override + public List getLoanCompoundingDetails() { + return null; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java index 7792416ed..db676777c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelPeriod.java @@ -19,8 +19,10 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; +import java.util.List; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.joda.time.LocalDate; @@ -51,4 +53,5 @@ public interface LoanScheduleModelPeriod { void addPrincipalAmount(Money principalDue); void addInterestAmount(Money interestDue); + List getLoanCompoundingDetails(); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java index 5b8208e83..201e7ca8b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelRepaymentPeriod.java @@ -19,8 +19,11 @@ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import org.apache.fineract.organisation.monetary.domain.Money; +import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.joda.time.LocalDate; @@ -40,6 +43,7 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel private Money penaltyChargesDue; private Money totalDue; private final boolean recalculatedInterestComponent; + private final List loanCompoundingDetails = new ArrayList<>(); public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate, final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, @@ -154,4 +158,8 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel this.interestDue = this.interestDue.plus(interestDue); this.totalDue = this.totalDue.plus(principalDue); } + @Override + public List getLoanCompoundingDetails() { + return this.loanCompoundingDetails; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java index 3761ce0d4..c82301d17 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; +import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.common.domain.DayOfWeekType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO; @@ -28,7 +29,8 @@ public interface ScheduledDateGenerator { LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO); LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, - final LocalDate firstRepaymentDate); + final LocalDate firstRepaymentDate, final Calendar loanCalendar, final HolidayDetailDTO holidayDetailDTO, + final LoanApplicationTerms loanApplicationTerms); LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment, final HolidayDetailDTO holidayDetailDTO); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 56248ec5f..b86f2a719 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -68,6 +68,7 @@ import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper; import org.apache.fineract.portfolio.common.domain.DayOfWeekType; import org.apache.fineract.portfolio.common.domain.DaysInMonthType; import org.apache.fineract.portfolio.common.domain.DaysInYearType; +import org.apache.fineract.portfolio.common.domain.NthDayType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO; import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData; @@ -115,7 +116,6 @@ import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.Days; import org.joda.time.LocalDate; -import org.joda.time.LocalTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -256,6 +256,12 @@ public class LoanScheduleAssembler { final PeriodFrequencyType meetingPeriodFrequency = CalendarUtils.getMeetingPeriodFrequencyType(calendar.getRecurrence()); validateRepaymentFrequencyIsSameAsMeetingFrequency(meetingPeriodFrequency.getValue(), repaymentFrequencyType, CalendarUtils.getInterval(calendar.getRecurrence()), repaymentEvery); + } else { + if (repaymentPeriodFrequencyType == PeriodFrequencyType.MONTHS && nthDay != null && nthDay != NthDayType.INVALID.getValue()) { + LocalDate calendarStartDate = repaymentsStartingFromDate; + if (calendarStartDate == null) calendarStartDate = expectedDisbursementDate; + calendar = createLoanCalendar(calendarStartDate, repaymentEvery, CalendarFrequencyType.MONTHLY, dayOfWeek, nthDay); + } } /* @@ -306,7 +312,8 @@ public class LoanScheduleAssembler { final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element); final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element); final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); - final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = this.configurationDomainService.isInterestChargedFromDateSameAsDisbursementDate(); + final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = this.configurationDomainService + .isInterestChargedFromDateSameAsDisbursementDate(); final Integer graceOnArrearsAgeing = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( LoanProductConstants.graceOnArrearsAgeingParameterName, element); @@ -334,39 +341,47 @@ public class LoanScheduleAssembler { CalendarInstance restCalendarInstance = null; RecalculationFrequencyType compoundingFrequencyType = null; CalendarInstance compoundingCalendarInstance = null; + InterestRecalculationCompoundingMethod compoundingMethod = null; + boolean allowCompoundingOnEod = false; if (isInterestRecalculationEnabled) { LoanProductInterestRecalculationDetails loanProductInterestRecalculationDetails = loanProduct .getProductInterestRecalculationDetails(); recalculationFrequencyType = loanProductInterestRecalculationDetails.getRestFrequencyType(); + Integer repeatsOnDay = null; + Integer recalculationFrequencyNthDay = loanProductInterestRecalculationDetails.getRestFrequencyOnDay(); + if (recalculationFrequencyNthDay == null) { + recalculationFrequencyNthDay = loanProductInterestRecalculationDetails.getRestFrequencyNthDay(); + repeatsOnDay = loanProductInterestRecalculationDetails.getRestFrequencyWeekday(); + } + Integer frequency = loanProductInterestRecalculationDetails.getRestInterval(); if (recalculationFrequencyType.isSameAsRepayment()) { restCalendarInstance = createCalendarForSameAsRepayment(repaymentEvery, repaymentPeriodFrequencyType, expectedDisbursementDate); } else { - LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( - LoanProductConstants.recalculationRestFrequencyDateParamName, element); - if (calendarStartDate == null) { - calendarStartDate = expectedDisbursementDate; - } + LocalDate calendarStartDate = expectedDisbursementDate; restCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, recalculationFrequencyType, - loanProductInterestRecalculationDetails.getRestInterval()); + frequency, recalculationFrequencyNthDay, repeatsOnDay); } - InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod - .fromInt(loanProductInterestRecalculationDetails.getInterestRecalculationCompoundingMethod()); + compoundingMethod = InterestRecalculationCompoundingMethod.fromInt(loanProductInterestRecalculationDetails + .getInterestRecalculationCompoundingMethod()); if (compoundingMethod.isCompoundingEnabled()) { + Integer compoundingRepeatsOnDay = null; + Integer recalculationCompoundingFrequencyNthDay = loanProductInterestRecalculationDetails.getCompoundingFrequencyOnDay(); + if (recalculationCompoundingFrequencyNthDay == null) { + recalculationCompoundingFrequencyNthDay = loanProductInterestRecalculationDetails.getCompoundingFrequencyNthDay(); + compoundingRepeatsOnDay = loanProductInterestRecalculationDetails.getCompoundingFrequencyWeekday(); + } compoundingFrequencyType = loanProductInterestRecalculationDetails.getCompoundingFrequencyType(); if (compoundingFrequencyType.isSameAsRepayment()) { compoundingCalendarInstance = createCalendarForSameAsRepayment(repaymentEvery, repaymentPeriodFrequencyType, expectedDisbursementDate); } else { - LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( - LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element); - if (calendarStartDate == null) { - calendarStartDate = expectedDisbursementDate; - } + LocalDate calendarStartDate = expectedDisbursementDate; compoundingCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, compoundingFrequencyType, - loanProductInterestRecalculationDetails.getCompoundingInterval()); + loanProductInterestRecalculationDetails.getCompoundingInterval(), recalculationCompoundingFrequencyNthDay, + compoundingRepeatsOnDay); } - + allowCompoundingOnEod = loanProductInterestRecalculationDetails.allowCompoundingOnEod(); } } @@ -405,6 +420,21 @@ public class LoanScheduleAssembler { } } + final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element); + Client client = null; + Long officeId = null; + if (clientId != null) { + client = this.clientRepository.findOneWithNotFoundDetection(clientId); + officeId = client.getOffice().getId(); + } else if (groupId != null) { + group = this.groupRepository.findOneWithNotFoundDetection(groupId); + officeId = group.getOffice().getId(); + } + final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, expectedDisbursementDate.toDate(), + HolidayStatusType.ACTIVE.getValue()); + final WorkingDays workingDays = this.workingDaysRepository.findOne(); + HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, @@ -412,21 +442,24 @@ public class LoanScheduleAssembler { calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, - recalculationFrequencyType, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, + recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), - calendar, BigDecimal.ZERO, loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled,numberOfDays, isSkipMeetingOnFirstDay); + calendar, BigDecimal.ZERO, loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled,numberOfDays, isSkipMeetingOnFirstDay, detailDTO, + allowCompoundingOnEod); } private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final LocalDate expectedDisbursementDate) { - + final Integer recalculationFrequencyNthDay = null; + final Integer repeatsOnDay = expectedDisbursementDate.getDayOfWeek(); CalendarInstance restCalendarInstance = createInterestRecalculationCalendarInstance(expectedDisbursementDate, repaymentEvery, - CalendarFrequencyType.from(repaymentPeriodFrequencyType)); + CalendarFrequencyType.from(repaymentPeriodFrequencyType), recalculationFrequencyNthDay, repeatsOnDay); return restCalendarInstance; } private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, - final RecalculationFrequencyType recalculationFrequencyType, final Integer frequency) { + final RecalculationFrequencyType recalculationFrequencyType, final Integer frequency, + final Integer recalculationFrequencyNthDay, final Integer repeatsOnDay) { CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID; switch (recalculationFrequencyType) { @@ -443,18 +476,26 @@ public class LoanScheduleAssembler { break; } - return createInterestRecalculationCalendarInstance(calendarStartDate, frequency, calendarFrequencyType); + return createInterestRecalculationCalendarInstance(calendarStartDate, frequency, calendarFrequencyType, + recalculationFrequencyNthDay, repeatsOnDay); } private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, final Integer frequency, - CalendarFrequencyType calendarFrequencyType) { - final Integer repeatsOnDay = calendarStartDate.getDayOfWeek(); + CalendarFrequencyType calendarFrequencyType, final Integer recalculationFrequencyNthDay, final Integer repeatsOnDay) { final String title = "loan_recalculation_detail"; final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), - calendarFrequencyType, frequency, repeatsOnDay); + calendarFrequencyType, frequency, repeatsOnDay, recalculationFrequencyNthDay); return CalendarInstance.from(calendar, null, CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); } + private Calendar createLoanCalendar(final LocalDate calendarStartDate, final Integer frequency, + CalendarFrequencyType calendarFrequencyType, final Integer repeatsOnDay, final Integer repeatsOnNthDayOfMonth) { + final String title = "loan_schedule"; + final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), + calendarFrequencyType, frequency, repeatsOnDay, repeatsOnNthDayOfMonth); + return calendar; + } + private List fetchDisbursementData(final JsonObject command) { final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command); final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command); @@ -559,7 +600,7 @@ public class LoanScheduleAssembler { validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays); validateDisbursementDateIsOnHoliday(loanApplicationTerms.getExpectedDisbursementDate(), isHolidayEnabled, holidays); - + Set loanDisbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject()); return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element, loanDisbursementDetails); @@ -996,10 +1037,10 @@ public class LoanScheduleAssembler { final LocalDate dateBasedOnMinimumDaysBetweenDisbursalAndFirstRepayment = expectedDisbursementDate .plusDays(minimumDaysBetweenDisbursalAndFirstRepayment); - if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendar != null) { + if (calendar != null) { final LocalDate refernceDateForCalculatingFirstRepaymentDate = expectedDisbursementDate; - derivedFirstRepayment = deriveFirstRepaymentDateForJLGLoans(repaymentEvery, expectedDisbursementDate, + derivedFirstRepayment = deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, refernceDateForCalculatingFirstRepaymentDate, repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar); @@ -1027,7 +1068,7 @@ public class LoanScheduleAssembler { return derivedFirstRepayment; } - private LocalDate deriveFirstRepaymentDateForJLGLoans(final Integer repaymentEvery, final LocalDate expectedDisbursementDate, + private LocalDate deriveFirstRepaymentDateForLoans(final Integer repaymentEvery, final LocalDate expectedDisbursementDate, final LocalDate refernceDateForCalculatingFirstRepaymentDate, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar) { boolean isMeetingSkipOnFirstDayOfMonth = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled(); @@ -1036,7 +1077,7 @@ public class LoanScheduleAssembler { final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar, refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency, isMeetingSkipOnFirstDayOfMonth, numberOfDays); final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment); - return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment : deriveFirstRepaymentDateForJLGLoans( + return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment : deriveFirstRepaymentDateForLoans( repaymentEvery, expectedDisbursementDate, derivedFirstRepayment, repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index 9ab13eea9..3a008ab8f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -63,6 +63,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStat import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; @@ -370,13 +371,13 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche final Collection periods = loanRescheduleModel.getPeriods(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); Collection waiveLoanCharges = new ArrayList<>(); - + final List compoundingDetails = null; for (LoanRescheduleModelRepaymentPeriod period : periods) { if (period.isNew()) { LoanRepaymentScheduleInstallment repaymentScheduleInstallment = new LoanRepaymentScheduleInstallment(loan, period.periodNumber(), period.periodFromDate(), period.periodDueDate(), period.principalDue(), - period.interestDue(), BigDecimal.ZERO, BigDecimal.ZERO, false); + period.interestDue(), BigDecimal.ZERO, BigDecimal.ZERO, false, compoundingDetails); repaymentScheduleInstallments.add(repaymentScheduleInstallment); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java index 48a5c516e..a03fe9b69 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -46,18 +46,29 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper { /** * The parameters supported for this command. */ - final Set supportedParameters = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanType", "calendarId", "productId", - "accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId", "principal", - "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency", - "loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", - LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "interestRateFrequencyType", "expectedDisbursementDate", - "repaymentsStartingFromDate", "graceOnPrincipalPayment", "recurringMoratoriumOnPrincipalPeriods", "graceOnInterestPayment", "graceOnInterestCharged", - "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", "charges", "collateral", - "syncDisbursementWithMeeting", "linkAccountId", LoanApiConstants.disbursementDataParameterName, + final Set supportedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.idParameterName, + LoanApiConstants.clientIdParameterName, LoanApiConstants.groupIdParameterName, LoanApiConstants.loanTypeParameterName, + LoanApiConstants.calendarIdParameterName, LoanApiConstants.productIdParameterName, LoanApiConstants.accountNoParameterName, + LoanApiConstants.externalIdParameterName, LoanApiConstants.fundIdParameterName, LoanApiConstants.loanOfficerIdParameterName, + LoanApiConstants.loanPurposeIdParameterName, LoanApiConstants.transactionProcessingStrategyIdParameterName, + LoanApiConstants.principalParamName, LoanApiConstants.inArrearsToleranceParameterName, + LoanApiConstants.interestRatePerPeriodParameterName, LoanApiConstants.repaymentEveryParameterName, + LoanApiConstants.numberOfRepaymentsParameterName, LoanApiConstants.loanTermFrequencyParameterName, + LoanApiConstants.loanTermFrequencyTypeParameterName, LoanApiConstants.repaymentFrequencyTypeParameterName, + LoanApiConstants.amortizationTypeParameterName, LoanApiConstants.interestTypeParameterName, + LoanApiConstants.interestCalculationPeriodTypeParameterName, + LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, LoanApiConstants.interestRateFrequencyTypeParameterName, + LoanApiConstants.disbursementDateParameterName, LoanApiConstants.repaymentsStartingFromDateParameterName, + LoanApiConstants.graceOnPrincipalPaymentParameterName, LoanApiConstants.graceOnInterestPaymentParameterName, + LoanApiConstants.graceOnInterestChargedParameterName, LoanApiConstants.interestChargedFromDateParameterName, + LoanApiConstants.submittedOnDateParameterName, LoanApiConstants.submittedOnNoteParameterName, + LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName, LoanApiConstants.chargesParameterName, + LoanApiConstants.collateralParameterName, LoanApiConstants.syncDisbursementWithMeetingParameterName, + LoanApiConstants.linkAccountIdParameterName, LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, - LoanProductConstants.graceOnArrearsAgeingParameterName, LoanProductConstants.recalculationRestFrequencyDateParamName, - "createStandingInstructionAtDisbursement", LoanApiConstants.isFloatingInterestRateParameterName, - LoanApiConstants.interestRateDifferentialParameterName)); + LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName, + LoanApiConstants.isFloatingInterestRateParameterName, LoanApiConstants.interestRateDifferentialParameterName, + LoanApiConstants.repaymentFrequencyNthDayTypeParameterName, LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName)); private final FromJsonHelper fromApiJsonHelper; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 6b2c4c4e4..0eaf4aff3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -36,10 +36,10 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati import org.apache.fineract.infrastructure.core.exception.UnsupportedParameterException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.accountdetails.domain.AccountType; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; -import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; @@ -60,34 +60,38 @@ public final class LoanApplicationCommandFromApiJsonHelper { /** * The parameters supported for this command. */ - 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", LoanApiConstants.isFloatingInterestRate, LoanApiConstants.interestRateDifferential, - "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, - "interestRateFrequencyType", "expectedDisbursementDate", "repaymentsStartingFromDate", - "graceOnPrincipalPayment", - "recurringMoratoriumOnPrincipalPeriods", - "graceOnInterestPayment", - "graceOnInterestCharged", - "interestChargedFromDate", - "submittedOnDate", - "submittedOnNote", - "accountNo", - "externalId", - "fundId", - "loanOfficerId", // optional - "loanPurposeId", - "inArrearsTolerance", - "charges", - "collateral", // optional - "transactionProcessingStrategyId", // settings - "calendarId", // optional - "syncDisbursementWithMeeting",// optional - "linkAccountId", LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, - LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName, - LoanProductConstants.recalculationRestFrequencyDateParamName, - LoanProductConstants.recalculationCompoundingFrequencyDateParamName, "createStandingInstructionAtDisbursement")); + final Set supportedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.dateFormatParameterName, + LoanApiConstants.localeParameterName, LoanApiConstants.idParameterName, LoanApiConstants.clientIdParameterName, + LoanApiConstants.groupIdParameterName, LoanApiConstants.loanTypeParameterName, LoanApiConstants.productIdParameterName, + LoanApiConstants.principalParamName, LoanApiConstants.loanTermFrequencyParameterName, + LoanApiConstants.loanTermFrequencyTypeParameterName, LoanApiConstants.numberOfRepaymentsParameterName, + LoanApiConstants.repaymentEveryParameterName, LoanApiConstants.repaymentFrequencyTypeParameterName, + LoanApiConstants.repaymentFrequencyNthDayTypeParameterName, LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, + LoanApiConstants.interestRatePerPeriodParameterName, LoanApiConstants.amortizationTypeParameterName, + LoanApiConstants.interestTypeParameterName, LoanApiConstants.isFloatingInterestRate, LoanApiConstants.interestRateDifferential, + LoanApiConstants.interestCalculationPeriodTypeParameterName, + LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, LoanApiConstants.interestRateFrequencyTypeParameterName, + LoanApiConstants.disbursementDateParameterName, LoanApiConstants.repaymentsStartingFromDateParameterName, + LoanApiConstants.graceOnPrincipalPaymentParameterName, + LoanApiConstants.graceOnInterestPaymentParameterName, + LoanApiConstants.graceOnInterestChargedParameterName, + LoanApiConstants.interestChargedFromDateParameterName, + LoanApiConstants.submittedOnDateParameterName, + LoanApiConstants.submittedOnNoteParameterName, + LoanApiConstants.accountNoParameterName, + LoanApiConstants.externalIdParameterName, + LoanApiConstants.fundIdParameterName, + LoanApiConstants.loanOfficerIdParameterName, // optional + LoanApiConstants.loanPurposeIdParameterName, + LoanApiConstants.inArrearsToleranceParameterName, + LoanApiConstants.chargesParameterName, + LoanApiConstants.collateralParameterName, // optional + LoanApiConstants.transactionProcessingStrategyIdParameterName, // settings + LoanApiConstants.calendarIdParameterName, // optional + LoanApiConstants.syncDisbursementWithMeetingParameterName,// optional + LoanApiConstants.linkAccountIdParameterName, LoanApiConstants.disbursementDataParameterName, + LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, + LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName)); private final FromJsonHelper fromApiJsonHelper; private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper; @@ -214,6 +218,10 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull() .inMinMaxRange(0, 3); + final String repaymentFrequencyNthDayTypeParameterName = "repaymentFrequencyNthDayType"; + final String repaymentFrequencyDayOfWeekTypeParameterName = "repaymentFrequencyDayOfWeekType"; + CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, repaymentFrequencyNthDayTypeParameterName, + repaymentFrequencyDayOfWeekTypeParameterName, element, this.fromApiJsonHelper); final String interestTypeParameterName = "interestType"; final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element); baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1); @@ -591,6 +599,10 @@ public final class LoanApplicationCommandFromApiJsonHelper { element); baseDataValidator.reset().parameter(repaymentEveryTypeParameterName).value(repaymentEveryType).notNull().inMinMaxRange(0, 3); } + final String repaymentFrequencyNthDayTypeParameterName = "repaymentFrequencyNthDayType"; + final String repaymentFrequencyDayOfWeekTypeParameterName = "repaymentFrequencyDayOfWeekType"; + CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, repaymentFrequencyNthDayTypeParameterName, + repaymentFrequencyDayOfWeekTypeParameterName, element, this.fromApiJsonHelper); final String interestTypeParameterName = "interestType"; Integer interestType = null; @@ -1095,14 +1107,6 @@ public final class LoanApplicationCommandFromApiJsonHelper { } - public void validateRecalcuationFrequency(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() - .validateDateBeforeOrEqual(expectedDisbursementDate); - } - public void validateLoanCharges(final Set charges, final List dataValidationErrors) { if (charges == null) { return; } final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); @@ -1139,32 +1143,11 @@ public final class LoanApplicationCommandFromApiJsonHelper { public void validateLoanForInterestRecalculation(final Loan loan) { final List dataValidationErrors = new ArrayList<>(); - 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); - } - private void validatePartialPeriodSupport(final Integer interestCalculationPeriodType, final DataValidatorBuilder baseDataValidator, final JsonElement element, final LoanProduct loanProduct) { if (interestCalculationPeriodType != null) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformService.java index 8f48eeb52..277fdd898 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformService.java @@ -33,5 +33,6 @@ public interface LoanAccrualPlatformService { void addAccrualAccounting() throws JobExecutionException; void addPeriodicAccruals() throws JobExecutionException; + void addPeriodicAccrualsForLoansWithIncomePostedAsTransactions() throws JobExecutionException; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java index a790f4ec7..7365d15a0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualPlatformServiceImpl.java @@ -117,4 +117,24 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic return sb.toString(); } + @Override + @CronTarget(jobName = JobName.ADD_PERIODIC_ACCRUAL_ENTRIES_FOR_LOANS_WITH_INCOME_POSTED_AS_TRANSACTIONS) + public void addPeriodicAccrualsForLoansWithIncomePostedAsTransactions() throws JobExecutionException { + Collection loanIds = this.loanReadPlatformService.retrieveLoanIdsWithPendingIncomePostingTransactions(); + if(loanIds != null && loanIds.size() > 0){ + StringBuilder sb = new StringBuilder(); + for (Long loanId : loanIds) { + try { + this.loanAccrualWritePlatformService.addIncomeAndAccrualTransactions(loanId); + } catch (Exception e) { + Throwable realCause = e; + if (e.getCause() != null) { + realCause = e.getCause(); + } + sb.append("failed to add income and accrual transaction for loan " + loanId + " with message " + realCause.getMessage()); + } + } + if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java index 1e63a7ce7..10442792c 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformService.java @@ -29,4 +29,5 @@ public interface LoanAccrualWritePlatformService { void addPeriodicAccruals(LocalDate tilldate, Long loanId, Collection loanScheduleAccrualDatas) throws Exception; + void addIncomeAndAccrualTransactions(Long loanId) throws Exception; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java index 79c54ffef..5ac0b6433 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualWritePlatformServiceImpl.java @@ -32,15 +32,22 @@ import javax.sql.DataSource; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; +import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper; +import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData; import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; +import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; +import org.apache.fineract.useradministration.domain.AppUserRepositoryWrapper; import org.joda.time.Days; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; @@ -56,16 +63,23 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat private final JdbcTemplate jdbcTemplate; private final DataSource dataSource; private final JournalEntryWritePlatformService journalEntryWritePlatformService; + private final AppUserRepositoryWrapper userRepository; + private final LoanRepository loanRepository; + private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository; @Autowired public LoanAccrualWritePlatformServiceImpl(final RoutingDataSource dataSource, final LoanReadPlatformService loanReadPlatformService, final JournalEntryWritePlatformService journalEntryWritePlatformService, - final LoanChargeReadPlatformService loanChargeReadPlatformService) { + final LoanChargeReadPlatformService loanChargeReadPlatformService, final AppUserRepositoryWrapper userRepository, + final LoanRepository loanRepository, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository) { this.loanReadPlatformService = loanReadPlatformService; this.dataSource = dataSource; this.jdbcTemplate = new JdbcTemplate(this.dataSource); this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.loanChargeReadPlatformService = loanChargeReadPlatformService; + this.userRepository = userRepository; + this.loanRepository = loanRepository; + this.applicationCurrencyRepository = applicationCurrencyRepository; } @Override @@ -469,4 +483,29 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat accrualData.updateAccruableIncome(interestIncome); } + @Override + @Transactional + public void addIncomeAndAccrualTransactions(Long loanId) throws Exception { + if (loanId != null) { + Loan loan = this.loanRepository.findOne(loanId); + if (loan == null) { throw new LoanNotFoundException(loanId); } + final List existingTransactionIds = new ArrayList<>(); + final List existingReversedTransactionIds = new ArrayList<>(); + existingTransactionIds.addAll(loan.findExistingTransactionIds()); + existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); + loan.processIncomeTransactions(this.userRepository.fetchSystemUser()); + this.loanRepository.saveAndFlush(loan); + postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + } + } + + private void postJournalEntries(final Loan loan, final List existingTransactionIds, + final List existingReversedTransactionIds) { + final MonetaryCurrency currency = loan.getCurrency(); + final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + boolean isAccountTransfer = false; + final Map accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(), + existingTransactionIds, existingReversedTransactionIds, isAccountTransfer); + this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 8d5ce1942..0bcdd09bf 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -55,6 +55,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; import org.apache.fineract.portfolio.calendar.domain.CalendarRepository; import org.apache.fineract.portfolio.calendar.domain.CalendarType; import org.apache.fineract.portfolio.calendar.exception.CalendarNotFoundException; +import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator; import org.apache.fineract.portfolio.client.domain.Client; @@ -64,6 +65,7 @@ import org.apache.fineract.portfolio.collateral.domain.LoanCollateral; import org.apache.fineract.portfolio.collateral.service.CollateralAssembler; import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_ENTITY; import org.apache.fineract.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.common.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.group.domain.Group; @@ -88,6 +90,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationNotInS import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified; import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService; @@ -108,7 +111,6 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; -import org.joda.time.LocalTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -153,6 +155,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final ConfigurationDomainService configurationDomainService; private final LoanScheduleAssembler loanScheduleAssembler; private final LoanUtilService loanUtilService; + private final CalendarReadPlatformService calendarReadPlatformService; @Autowired public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, @@ -171,7 +174,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final LoanReadPlatformService loanReadPlatformService, final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, final BusinessEventNotifierService businessEventNotifierService, final ConfigurationDomainService configurationDomainService, - final LoanScheduleAssembler loanScheduleAssembler, final LoanUtilService loanUtilService) { + final LoanScheduleAssembler loanScheduleAssembler, final LoanUtilService loanUtilService, + final CalendarReadPlatformService calendarReadPlatformService) { this.context = context; this.fromJsonHelper = fromJsonHelper; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; @@ -201,6 +205,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.configurationDomainService = configurationDomainService; this.loanScheduleAssembler = loanScheduleAssembler; this.loanUtilService = loanUtilService; + this.calendarReadPlatformService = calendarReadPlatformService; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -288,6 +293,26 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final CalendarInstance calendarInstance = new CalendarInstance(calendar, newLoanApplication.getId(), CalendarEntityType.LOANS.getValue()); this.calendarInstanceRepository.save(calendarInstance); + } else { + final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(command.parsedJson()); + final Integer repaymentFrequencyNthDayType = command.integerValueOfParameterNamed("repaymentFrequencyNthDayType"); + if (loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.MONTHS + && repaymentFrequencyNthDayType != null) { + final String title = "loan_schedule_" + newLoanApplication.getId(); + LocalDate calendarStartDate = loanApplicationTerms.getRepaymentsStartingFromLocalDate(); + if (calendarStartDate == null) calendarStartDate = loanApplicationTerms.getExpectedDisbursementDate(); + final CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.MONTHLY; + final Integer frequency = loanApplicationTerms.getRepaymentEvery(); + final Integer repeatsOnDay = loanApplicationTerms.getWeekDayType().getValue(); + final Integer repeatsOnNthDayOfMonth = loanApplicationTerms.getNthDay(); + final Integer calendarEntityType = CalendarEntityType.LOANS.getValue(); + final Calendar loanCalendar = Calendar.createRepeatingCalendar(title, calendarStartDate, + CalendarType.COLLECTION.getValue(), calendarFrequencyType, frequency, repeatsOnDay, repeatsOnNthDayOfMonth); + this.calendarRepository.save(loanCalendar); + final CalendarInstance calendarInstance = CalendarInstance.from(loanCalendar, newLoanApplication.getId(), + calendarEntityType); + this.calendarInstanceRepository.save(calendarInstance); + } } // Save linked account information @@ -357,43 +382,49 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private void createAndPersistCalendarInstanceForInterestRecalculation(final Loan loan) { - LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getRestFrequencyLocalDate(); - if (calendarStartDate == null) { - calendarStartDate = loan.getExpectedDisbursedOnLocalDate(); - } - final Integer repeatsOnDay = calendarStartDate.getDayOfWeek(); + LocalDate calendarStartDate = loan.getExpectedDisbursedOnLocalDate(); + Integer repeatsOnDay = null; final RecalculationFrequencyType recalculationFrequencyType = loan.loanInterestRecalculationDetails().getRestFrequencyType(); + Integer recalculationFrequencyNthDay = loan.loanInterestRecalculationDetails().getRestFrequencyOnDay(); + if (recalculationFrequencyNthDay == null) { + recalculationFrequencyNthDay = loan.loanInterestRecalculationDetails().getRestFrequencyNthDay(); + repeatsOnDay = loan.loanInterestRecalculationDetails().getRestFrequencyWeekday(); + } 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); + createCalendar(loan, calendarStartDate, recalculationFrequencyNthDay, 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(); + LocalDate compoundingStartDate = loan.getExpectedDisbursedOnLocalDate(); + Integer compoundingRepeatsOnDay = null; final RecalculationFrequencyType recalculationCompoundingFrequencyType = loan.loanInterestRecalculationDetails() .getCompoundingFrequencyType(); + Integer recalculationCompoundingFrequencyNthDay = loan.loanInterestRecalculationDetails().getCompoundingFrequencyOnDay(); + if (recalculationCompoundingFrequencyNthDay == null) { + recalculationCompoundingFrequencyNthDay = loan.loanInterestRecalculationDetails().getCompoundingFrequencyNthDay(); + compoundingRepeatsOnDay = loan.loanInterestRecalculationDetails().getCompoundingFrequencyWeekday(); + } 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); + createCalendar(loan, compoundingStartDate, recalculationCompoundingFrequencyNthDay, 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) { + private void createCalendar(final Loan loan, LocalDate calendarStartDate, Integer recalculationFrequencyNthDay, + final Integer repeatsOnDay, final RecalculationFrequencyType recalculationFrequencyType, Integer frequency, + CalendarEntityType calendarEntityType, final String title) { CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID; + Integer updatedRepeatsOnDay = repeatsOnDay; switch (recalculationFrequencyType) { case DAILY: calendarFrequencyType = CalendarFrequencyType.DAILY; @@ -405,6 +436,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa frequency = loan.repaymentScheduleDetail().getRepayEvery(); calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType()); calendarStartDate = loan.getExpectedDisbursedOnLocalDate(); + if (updatedRepeatsOnDay == null) { + updatedRepeatsOnDay = calendarStartDate.getDayOfWeek(); + } break; case WEEKLY: calendarFrequencyType = CalendarFrequencyType.WEEKLY; @@ -414,71 +448,12 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), - calendarFrequencyType, frequency, repeatsOnDay); + calendarFrequencyType, frequency, updatedRepeatsOnDay, recalculationFrequencyNthDay); final CalendarInstance calendarInstance = CalendarInstance.from(calendar, loan.loanInterestRecalculationDetails().getId(), calendarEntityType.getValue()); this.calendarInstanceRepository.save(calendarInstance); } - private void updateRestCalendarDetailsForInterestRecalculation(final CalendarInstance calendarInstance, final Loan loan) { - - Calendar interestRecalculationRecurrings = calendarInstance.getCalendar(); - LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getRestFrequencyLocalDate(); - if (calendarStartDate == null) { - calendarStartDate = loan.getExpectedDisbursedOnLocalDate(); - } - final Integer repeatsOnDay = calendarStartDate.getDayOfWeek(); - 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: - calendarFrequencyType = CalendarFrequencyType.DAILY; - break; - - case MONTHLY: - calendarFrequencyType = CalendarFrequencyType.MONTHLY; - break; - case SAME_AS_REPAYMENT_PERIOD: - calendarStartDate = loan.getExpectedDisbursedOnLocalDate(); - frequency = loan.repaymentScheduleDetail().getRepayEvery(); - calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType()); - break; - case WEEKLY: - calendarFrequencyType = CalendarFrequencyType.WEEKLY; - break; - default: - break; - } - - interestRecalculationRecurrings.updateRepeatingCalendar(calendarStartDate, calendarFrequencyType, frequency, repeatsOnDay); - this.calendarRepository.save(interestRecalculationRecurrings); - } - @Transactional @Override public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) { @@ -551,7 +526,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } } } - + final Set possiblyModifedLoanCollateralItems = this.loanCollateralAssembler .fromParsedJson(command.parsedJson()); @@ -700,6 +675,11 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa // loan if (ciList != null && !ciList.isEmpty()) { final CalendarInstance calendarInstance = ciList.get(0); + final boolean isCalendarAssociatedWithEntity = this.calendarReadPlatformService.isCalendarAssociatedWithEntity(calendarInstance + .getEntityId(), calendarInstance.getCalendar().getId(), CalendarEntityType.LOANS.getValue().longValue()); + if (isCalendarAssociatedWithEntity) { + this.calendarRepository.delete(calendarInstance.getCalendar()); + } if (calendarInstance.getCalendar().getId() != calendar.getId()) { calendarInstance.updateCalendar(calendar); this.calendarInstanceRepository.saveAndFlush(calendarInstance); @@ -711,9 +691,65 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.calendarInstanceRepository.save(calendarInstance); } - } else if (ciList != null && !ciList.isEmpty()) { - final CalendarInstance calendarInstance = ciList.get(0); - this.calendarInstanceRepository.delete(calendarInstance); + } else { + if (ciList != null && !ciList.isEmpty()) { + final CalendarInstance existingCalendarInstance = ciList.get(0); + final boolean isCalendarAssociatedWithEntity = this.calendarReadPlatformService.isCalendarAssociatedWithEntity( + existingCalendarInstance.getEntityId(), existingCalendarInstance.getCalendar().getId(), + CalendarEntityType.GROUPS.getValue().longValue()); + if (isCalendarAssociatedWithEntity) { + this.calendarInstanceRepository.delete(existingCalendarInstance); + } + } + if (changes.containsKey("repaymentFrequencyNthDayType") || changes.containsKey("repaymentFrequencyDayOfWeekType")) { + if (changes.get("repaymentFrequencyNthDayType") == null) { + if (ciList != null && !ciList.isEmpty()) { + final CalendarInstance calendarInstance = ciList.get(0); + final boolean isCalendarAssociatedWithEntity = this.calendarReadPlatformService.isCalendarAssociatedWithEntity( + calendarInstance.getEntityId(), calendarInstance.getCalendar().getId(), CalendarEntityType.LOANS.getValue().longValue()); + if (isCalendarAssociatedWithEntity) { + this.calendarInstanceRepository.delete(calendarInstance); + this.calendarRepository.delete(calendarInstance.getCalendar()); + } + } + } else { + Integer repaymentFrequencyTypeInt = command.integerValueOfParameterNamed("repaymentFrequencyType"); + if (repaymentFrequencyTypeInt != null) { + if (PeriodFrequencyType.fromInt(repaymentFrequencyTypeInt) == PeriodFrequencyType.MONTHS) { + final String title = "loan_schedule_" + existingLoanApplication.getId(); + final Integer typeId = CalendarType.COLLECTION.getValue(); + final CalendarFrequencyType repaymentFrequencyType = CalendarFrequencyType.MONTHLY; + final Integer interval = command.integerValueOfParameterNamed("repaymentEvery"); + LocalDate startDate = command.localDateValueOfParameterNamed("repaymentsStartingFromDate"); + if (startDate == null) startDate = command.localDateValueOfParameterNamed("expectedDisbursementDate"); + final Calendar newCalendar = Calendar.createRepeatingCalendar(title, startDate, typeId, + repaymentFrequencyType, interval, (Integer) changes.get("repaymentFrequencyDayOfWeekType"), + (Integer) changes.get("repaymentFrequencyNthDayType")); + if (ciList != null && !ciList.isEmpty()) { + final CalendarInstance calendarInstance = ciList.get(0); + final boolean isCalendarAssociatedWithEntity = this.calendarReadPlatformService.isCalendarAssociatedWithEntity( + calendarInstance.getEntityId(), calendarInstance.getCalendar().getId(), CalendarEntityType.LOANS.getValue().longValue()); + if (isCalendarAssociatedWithEntity) { + final Calendar existingCalendar = calendarInstance.getCalendar(); + if (existingCalendar != null) { + String existingRecurrence = existingCalendar.getRecurrence(); + if (!existingRecurrence.equals(newCalendar.getRecurrence())) { + existingCalendar.setRecurrence(newCalendar.getRecurrence()); + this.calendarRepository.save(existingCalendar); + } + } + } + } else { + this.calendarRepository.save(newCalendar); + final Integer calendarEntityType = CalendarEntityType.LOANS.getValue(); + final CalendarInstance calendarInstance = new CalendarInstance(newCalendar, + existingLoanApplication.getId(), calendarEntityType); + this.calendarInstanceRepository.save(calendarInstance); + } + } + } + } + } } // Save linked account information @@ -776,20 +812,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.fromApiJsonDeserializer.validateLoanForInterestRecalculation(existingLoanApplication); if (changes.containsKey(LoanProductConstants.isInterestRecalculationEnabledParameterName)) { createAndPersistCalendarInstanceForInterestRecalculation(existingLoanApplication); - } else { - 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/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java index 755a75165..93df756d9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java @@ -70,7 +70,6 @@ import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementData import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; -import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; @@ -82,6 +81,7 @@ import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; + import com.google.gson.JsonElement; @Service @@ -282,10 +282,6 @@ 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(); @@ -297,8 +293,7 @@ public class LoanAssembler { final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element,disbursementDetails); loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(), - submittedOnDate, externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, - recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate); + submittedOnDate, externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay); return loanApplication; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java index e41860da9..f6df4e4da 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -126,4 +126,5 @@ public interface LoanReadPlatformService { LoanTransactionData retrieveRefundByCashTemplate(Long loanId); Collection retrieveLoanInterestRatePeriodData(Long loanId); + Collection retrieveLoanIdsWithPendingIncomePostingTransactions(); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 772ea775e..ac732b515 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -550,7 +550,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " l.principal_amount_proposed as proposedPrincipal, l.principal_amount as principal, l.approved_principal as approvedPrincipal, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery," + " l.grace_on_principal_periods as graceOnPrincipalPayment, l.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,l.grace_on_arrears_ageing as graceOnArrearsAgeing," + " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, " - + " l.repayment_period_frequency_enum as repaymentFrequencyType, l.repayment_frequency_nth_day_enum as repaymentFrequencyNthDayType, l.repayment_frequency_day_of_week_enum as repaymentFrequencyDayOfWeekType, l.interest_period_frequency_enum as interestRateFrequencyType, " + + " l.repayment_period_frequency_enum as repaymentFrequencyType, l.interest_period_frequency_enum as interestRateFrequencyType, " + " l.term_frequency as termFrequency, l.term_period_frequency_enum as termPeriodFrequencyType, " + " l.amortization_method_enum as amortizationType, l.interest_method_enum as interestType, l.interest_calculated_in_period_enum as interestCalculationPeriodType," + " l.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion," @@ -600,9 +600,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " l.interest_recalculation_enabled as isInterestRecalculationEnabled, " + " 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.rest_frequency_nth_day_enum as restFrequencyNthDayEnum, " + + " lir.rest_frequency_weekday_enum as restFrequencyWeekDayEnum, " + + " lir.rest_frequency_on_day as restFrequencyOnDay, " + " lir.compounding_frequency_type_enum as compoundingFrequencyEnum, lir.compounding_frequency_interval as compoundingInterval, " - + " lir.compounding_freqency_date as compoundingFrequencyDate, " + + " lir.compounding_frequency_nth_day_enum as compoundingFrequencyNthDayEnum, " + + " lir.compounding_frequency_weekday_enum as compoundingFrequencyWeekDayEnum, " + + " lir.compounding_frequency_on_day as compoundingFrequencyOnDay, " + + " lir.is_compounding_to_be_posted_as_transaction as isCompoundingToBePostedAsTransaction, " + + " lir.allow_compounding_on_eod as allowCompoundingOnEod, " + " l.is_floating_interest_rate as isFloatingInterestRate, " + " l.interest_rate_differential as interestRateDifferential, " + " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement, " @@ -752,14 +758,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final int repaymentFrequencyTypeInt = JdbcSupport.getInteger(rs, "repaymentFrequencyType"); final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeInt); - final Integer repaymentFrequencyNthDayTypeInt = JdbcSupport.getInteger(rs, "repaymentFrequencyNthDayType"); - final EnumOptionData repaymentFrequencyNthDayType = LoanEnumerations - .repaymentFrequencyNthDayType(repaymentFrequencyNthDayTypeInt); - - final Integer repaymentFrequencyDayOfWeekTypeInt = JdbcSupport.getInteger(rs, "repaymentFrequencyDayOfWeekType"); - final EnumOptionData repaymentFrequencyDayOfWeekType = LoanEnumerations - .repaymentFrequencyDayOfWeekType(repaymentFrequencyDayOfWeekTypeInt); - final int interestRateFrequencyTypeInt = JdbcSupport.getInteger(rs, "interestRateFrequencyType"); final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(interestRateFrequencyTypeInt); @@ -877,7 +875,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final int restFrequencyEnumValue = JdbcSupport.getInteger(rs, "restFrequencyEnum"); final EnumOptionData restFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(restFrequencyEnumValue); final int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval"); - final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate"); + final Integer restFrequencyNthDayEnumValue = JdbcSupport.getInteger(rs, "restFrequencyNthDayEnum"); + EnumOptionData restFrequencyNthDayEnum = null; + if (restFrequencyNthDayEnumValue != null) { + restFrequencyNthDayEnum = LoanEnumerations.interestRecalculationCompoundingNthDayType(restFrequencyNthDayEnumValue); + } + final Integer restFrequencyWeekDayEnumValue = JdbcSupport.getInteger(rs, "restFrequencyWeekDayEnum"); + EnumOptionData restFrequencyWeekDayEnum = null; + if (restFrequencyWeekDayEnumValue != null) { + restFrequencyWeekDayEnum = LoanEnumerations + .interestRecalculationCompoundingDayOfWeekType(restFrequencyWeekDayEnumValue); + } + final Integer restFrequencyOnDay = JdbcSupport.getInteger(rs, "restFrequencyOnDay"); final CalendarData compoundingCalendarData = null; final Integer compoundingFrequencyEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyEnum"); EnumOptionData compoundingFrequencyType = null; @@ -885,25 +894,41 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { compoundingFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(compoundingFrequencyEnumValue); } final Integer compoundingInterval = JdbcSupport.getInteger(rs, "compoundingInterval"); - final LocalDate compoundingFrequencyDate = JdbcSupport.getLocalDate(rs, "compoundingFrequencyDate"); + final Integer compoundingFrequencyNthDayEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyNthDayEnum"); + EnumOptionData compoundingFrequencyNthDayEnum = null; + if (compoundingFrequencyNthDayEnumValue != null) { + compoundingFrequencyNthDayEnum = LoanEnumerations + .interestRecalculationCompoundingNthDayType(compoundingFrequencyNthDayEnumValue); + } + final Integer compoundingFrequencyWeekDayEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyWeekDayEnum"); + EnumOptionData compoundingFrequencyWeekDayEnum = null; + if (compoundingFrequencyWeekDayEnumValue != null) { + compoundingFrequencyWeekDayEnum = LoanEnumerations + .interestRecalculationCompoundingDayOfWeekType(compoundingFrequencyWeekDayEnumValue); + } + final Integer compoundingFrequencyOnDay = JdbcSupport.getInteger(rs, "compoundingFrequencyOnDay"); + final Boolean isCompoundingToBePostedAsTransaction = rs.getBoolean("isCompoundingToBePostedAsTransaction"); + final Boolean allowCompoundingOnEod = rs.getBoolean("allowCompoundingOnEod"); interestRecalculationData = new LoanInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType, - rescheduleStrategyType, calendarData, restFrequencyType, restFrequencyInterval, restFrequencyDate, - compoundingCalendarData, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate); + rescheduleStrategyType, calendarData, restFrequencyType, restFrequencyInterval, restFrequencyNthDayEnum, + restFrequencyWeekDayEnum, restFrequencyOnDay, compoundingCalendarData, compoundingFrequencyType, + compoundingInterval, compoundingFrequencyNthDayEnum, compoundingFrequencyWeekDayEnum, compoundingFrequencyOnDay, + isCompoundingToBePostedAsTransaction, allowCompoundingOnEod); } return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency, - termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, - repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType, - interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, - interestRateDifferential, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, - expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, - interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, - loanCounter, loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, - inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, + termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, null, null, transactionStrategyId, + transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, + interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, + allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, + recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, + timeline, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, + loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears, + graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, maximumGap); } @@ -1569,10 +1594,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { sqlBuilder .append("select ") .append(mapper.schema()) - .append(" where ((ls.fee_charges_amount <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))") + .append(" where (recaldet.is_compounding_to_be_posted_as_transaction is null or recaldet.is_compounding_to_be_posted_as_transaction = 0) ") + .append(" and (((ls.fee_charges_amount <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))") .append(" or ( ls.penalty_charges_amount <> if(ls.accrual_penalty_charges_derived is null,0,ls.accrual_penalty_charges_derived))") .append(" or ( ls.interest_amount <> if(ls.accrual_interest_derived is null,0,ls.accrual_interest_derived)))") - .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=0 and ls.duedate <= CURDATE() "); + .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=0 and ls.duedate <= CURDATE()) "); if(organisationStartDate != null){ sqlBuilder.append(" and ls.duedate > :organisationstartdate "); } @@ -1594,11 +1620,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { sqlBuilder .append("select ") .append(mapper.schema()) - .append(" where ((ls.fee_charges_amount <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))") + .append(" where (recaldet.is_compounding_to_be_posted_as_transaction is null or recaldet.is_compounding_to_be_posted_as_transaction = 0) ") + .append(" and (((ls.fee_charges_amount <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))") .append(" or (ls.penalty_charges_amount <> if(ls.accrual_penalty_charges_derived is null,0,ls.accrual_penalty_charges_derived))") .append(" or (ls.interest_amount <> if(ls.accrual_interest_derived is null,0,ls.accrual_interest_derived)))") .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and (loan.closedon_date <= :tilldate or loan.closedon_date is null)") - .append(" and loan.is_npa=0 and (ls.duedate <= :tilldate or (ls.duedate > :tilldate and ls.fromdate < :tilldate)) "); + .append(" and loan.is_npa=0 and (ls.duedate <= :tilldate or (ls.duedate > :tilldate and ls.fromdate < :tilldate))) "); if(organisationStartDate != null){ sqlBuilder.append(" and ls.duedate > :organisationstartdate "); } @@ -1632,7 +1659,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { .append(" from m_loan_repayment_schedule ls ").append(" left join m_loan loan on loan.id=ls.loan_id ") .append(" left join m_product_loan mpl on mpl.id = loan.product_id") .append(" left join m_client mc on mc.id = loan.client_id ").append(" left join m_group mg on mg.id = loan.group_id") - .append(" left join m_currency curr on curr.code = loan.currency_code"); + .append(" left join m_currency curr on curr.code = loan.currency_code") + .append(" left join m_loan_recalculation_details as recaldet on loan.id = recaldet.loan_id "); return sqlBuilder.toString(); } @@ -1693,7 +1721,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { .append(" from m_loan_repayment_schedule ls ").append(" left join m_loan loan on loan.id=ls.loan_id ") .append(" left join m_product_loan mpl on mpl.id = loan.product_id") .append(" left join m_client mc on mc.id = loan.client_id ").append(" left join m_group mg on mg.id = loan.group_id") - .append(" left join m_currency curr on curr.code = loan.currency_code"); + .append(" left join m_currency curr on curr.code = loan.currency_code") + .append(" left join m_loan_recalculation_details as recaldet on loan.id = recaldet.loan_id "); return sqlBuilder.toString(); } @@ -2034,4 +2063,24 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } } + @Override + public Collection retrieveLoanIdsWithPendingIncomePostingTransactions() { + StringBuilder sqlBuilder = new StringBuilder() + .append(" select distinct loan.id ") + .append(" from m_loan as loan ") + .append(" inner join m_loan_recalculation_details as recdet on (recdet.loan_id = loan.id and recdet.is_compounding_to_be_posted_as_transaction is not null and recdet.is_compounding_to_be_posted_as_transaction = 1) ") + .append(" inner join m_loan_repayment_schedule as repsch on repsch.loan_id = loan.id ") + .append(" inner join m_loan_interest_recalculation_additional_details as adddet on adddet.loan_repayment_schedule_id = repsch.id ") + .append(" left join m_loan_transaction as trans on (trans.is_reversed <> 1 and trans.transaction_type_enum = 19 and trans.loan_id = loan.id and trans.transaction_date = adddet.effective_date) ") + .append(" where loan.loan_status_id = 300 ") + .append(" and adddet.effective_date is not null ") + .append(" and trans.transaction_date is null ") + .append(" and adddet.effective_date < ? "); + try { + String currentdate = formatter.print(DateUtils.getLocalDateOfTenant()); + return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { currentdate }); + } catch (final EmptyResultDataAccessException e) { + return null; + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index b6c7ba15d..16991820f 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -90,14 +90,14 @@ import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeUpdatedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeDeletedException; -import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBePayedException; -import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeUpdatedException; -import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeWaivedException; -import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException; import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeDeletedException.LOAN_CHARGE_CANNOT_BE_DELETED_REASON; +import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBePayedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBePayedException.LOAN_CHARGE_CANNOT_BE_PAYED_REASON; +import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeUpdatedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeUpdatedException.LOAN_CHARGE_CANNOT_BE_UPDATED_REASON; +import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeWaivedException; import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeWaivedException.LOAN_CHARGE_CANNOT_BE_WAIVED_REASON; +import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException; import org.apache.fineract.portfolio.client.domain.Client; import org.apache.fineract.portfolio.client.exception.ClientNotActiveException; import org.apache.fineract.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand; @@ -126,6 +126,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent; import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; import org.apache.fineract.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -304,7 +305,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Loan loan = this.loanAssembler.assembleFrom(loanId); checkClientOrGroupActive(loan); - + final LocalDate nextPossibleRepaymentDate = loan.getNextPossibleRepaymentDateForRescheduling(); final Date rescheduledRepaymentDate = command.DateValueOfParameterNamed("adjustRepaymentDate"); @@ -336,8 +337,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Map changes = new LinkedHashMap<>(); final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes); - - final Boolean isPaymnetypeApplicableforDisbursementCharge=configurationDomainService.isPaymnetypeApplicableforDisbursementCharge(); + + final Boolean isPaymnetypeApplicableforDisbursementCharge = configurationDomainService + .isPaymnetypeApplicableforDisbursementCharge(); updateLoanCounters(loan, actualDisbursementDate); Money amountBeforeAdjust = loan.getPrincpal(); @@ -366,10 +368,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(), loan, null); } - if(isPaymnetypeApplicableforDisbursementCharge){ - changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO,paymentDetail); - }else{ - changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO,null); + if (isPaymnetypeApplicableforDisbursementCharge) { + changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail); + } else { + changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null); } } if (!changes.isEmpty()) { @@ -549,7 +551,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final SingleDisbursalCommand[] disbursalCommand = bulkDisbursalCommand.getDisburseTransactions(); final Map changes = new LinkedHashMap<>(); if (disbursalCommand == null) { return changes; } - + final LocalDate nextPossibleRepaymentDate = null; final Date rescheduledRepaymentDate = null; @@ -597,15 +599,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } LocalDate recalculateFrom = null; final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); - regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate, rescheduledRepaymentDate); + regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate, + rescheduledRepaymentDate); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive( loan.fetchRepaymentScheduleInstallments(), loan, null); } - if(configurationDomainService.isPaymnetypeApplicableforDisbursementCharge()){ - changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO,paymentDetail); - }else{ - changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO,null); + if (configurationDomainService.isPaymnetypeApplicableforDisbursementCharge()) { + changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, paymentDetail); + } else { + changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO, null); } } if (!changes.isEmpty()) { @@ -1990,8 +1993,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf // loop through each loan to reschedule the repayment dates for (final Loan loan : loans) { if (loan != null) { - - if(loan.getExpectedFirstRepaymentOnDate() != null && loan.getExpectedFirstRepaymentOnDate().equals(presentMeetingDate)){ + if (loan.getExpectedFirstRepaymentOnDate() != null && loan.getExpectedFirstRepaymentOnDate().equals(presentMeetingDate)) { final String defaultUserMessage = "Meeting calendar date update is not supported since its a first repayment date"; throw new CalendarParameterUpdateNotSupportedException("meeting.for.first.repayment.date", defaultUserMessage, loan.getExpectedFirstRepaymentOnDate(), presentMeetingDate); @@ -2337,9 +2339,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf BigDecimal interest = BigDecimal.ZERO; BigDecimal feeCharges = BigDecimal.ZERO; BigDecimal penaltyCharges = BigDecimal.ONE; + final List compoundingDetails = null; LoanRepaymentScheduleInstallment newEntry = new LoanRepaymentScheduleInstallment(loan, installments.size() + 1, lastInstallment.getDueDate(), lastChargeDate, principal, interest, feeCharges, penaltyCharges, - recalculatedInterestComponent); + recalculatedInterestComponent, compoundingDetails); installments.add(newEntry); } } @@ -2548,8 +2551,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (command.entityId() != null) { - changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, - changes, scheduleGeneratorDTO, currentUser); + changedTransactionDetail = loan.updateDisbursementDateAndAmountForTranche(loanDisbursementDetails, command, changes, + scheduleGeneratorDTO, currentUser); } else { // BigDecimal setAmount = loan.getApprovedPrincipal(); Collection loanDisburseDetails = loan.getDisbursementDetails(); @@ -2735,8 +2738,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf AppUser currentUser = getAppUserIfPresent(); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName); - loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount, currentUser, - nextPossibleRepaymentDate, rescheduledRepaymentDate); + loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount, currentUser, + nextPossibleRepaymentDate, rescheduledRepaymentDate); } private List retrieveRepaymentScheduleFromModel(LoanScheduleModel model) { @@ -2747,7 +2750,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), - scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent()); + scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(), + scheduledLoanInstallment.getLoanCompoundingDetails()); installments.add(installment); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java index f476e4646..f704925b5 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java @@ -86,12 +86,17 @@ public interface LoanProductConstants { public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod"; public static final String recalculationRestFrequencyTypeParameterName = "recalculationRestFrequencyType"; public static final String recalculationRestFrequencyIntervalParameterName = "recalculationRestFrequencyInterval"; - public static final String recalculationRestFrequencyDateParamName = "recalculationRestFrequencyDate"; + public static final String recalculationRestFrequencyWeekdayParamName = "recalculationRestFrequencyDayOfWeekType"; + public static final String recalculationRestFrequencyNthDayParamName = "recalculationRestFrequencyNthDayType"; + public static final String recalculationRestFrequencyOnDayParamName = "recalculationRestFrequencyOnDayType"; 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"; + public static final String recalculationCompoundingFrequencyWeekdayParamName = "recalculationCompoundingFrequencyDayOfWeekType"; + public static final String recalculationCompoundingFrequencyNthDayParamName = "recalculationCompoundingFrequencyNthDayType"; + public static final String recalculationCompoundingFrequencyOnDayParamName = "recalculationCompoundingFrequencyOnDayType"; + public static final String isCompoundingToBePostedAsTransactionParamName = "isCompoundingToBePostedAsTransaction"; // Guarantee related public static final String holdGuaranteeFundsParamName = "holdGuaranteeFunds"; @@ -115,6 +120,7 @@ public interface LoanProductConstants { public static final String inArrearsToleranceParamName = "inArrearsTolerance"; public static final String repaymentEveryParamName = "repaymentEvery"; public static final String graceOnPrincipalAndInterestPaymentParamName = "graceOnPrincipalAndInterestPayment"; + public static final String allowCompoundingOnEodParamName = "allowCompoundingOnEod"; //Variable Installments Settings public static final String allowVariableInstallmentsParamName = "allowVariableInstallments" ; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java index a7d18160e..a269d549b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -298,6 +298,10 @@ public class LoanProductsApiResource { final List rescheduleStrategyTypeOptions = dropdownReadPlatformService.retrieveRescheduleStrategyTypeOptions(); final List interestRecalculationFrequencyTypeOptions = dropdownReadPlatformService .retrieveInterestRecalculationFrequencyTypeOptions(); + final List interestRecalculationNthDayTypeOptions = dropdownReadPlatformService + .retrieveInterestRecalculationNthDayTypeOptions(); + final List interestRecalculationDayOfWeekTypeOptions = dropdownReadPlatformService + .retrieveInterestRecalculationDayOfWeekTypeOptions(); final List preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService .retrivePreCloseInterestCalculationStrategyOptions(); final List floatingRateOptions = this.floatingRateReadPlatformService.retrieveLookupActive(); @@ -307,7 +311,8 @@ public class LoanProductsApiResource { interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions, accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions, interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions, - preCloseInterestCalculationStrategyOptions, floatingRateOptions); + preCloseInterestCalculationStrategyOptions, floatingRateOptions, interestRecalculationNthDayTypeOptions, + interestRecalculationDayOfWeekTypeOptions); } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java index 48f730ae2..24189452b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java @@ -159,6 +159,10 @@ public class LoanProductData { @SuppressWarnings("unused") private final List interestRecalculationCompoundingTypeOptions; @SuppressWarnings("unused") + private final List interestRecalculationNthDayTypeOptions; + @SuppressWarnings("unused") + private final List interestRecalculationDayOfWeekTypeOptions; + @SuppressWarnings("unused") private final List rescheduleStrategyTypeOptions; @SuppressWarnings("unused") private final List preClosureInterestCalculationStrategyOptions; @@ -585,6 +589,8 @@ public class LoanProductData { this.interestRecalculationCompoundingTypeOptions = null; this.rescheduleStrategyTypeOptions = null; this.interestRecalculationFrequencyTypeOptions = null; + this.interestRecalculationNthDayTypeOptions = null; + this.interestRecalculationDayOfWeekTypeOptions = null; this.canDefineInstallmentAmount = canDefineInstallmentAmount; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; @@ -602,7 +608,9 @@ public class LoanProductData { final List valueConditionTypeOptions, final List daysInMonthTypeOptions, final List daysInYearTypeOptions, final List interestRecalculationCompoundingTypeOptions, final List rescheduleStrategyTypeOptions, final List interestRecalculationFrequencyTypeOptions, - final List preCloseInterestCalculationStrategyOptions, final List floatingRateOptions) { + final List preCloseInterestCalculationStrategyOptions, final List floatingRateOptions, + final List interestRecalculationNthDayTypeOptions, + final List interestRecalculationDayOfWeekTypeOptions) { this.id = productData.id; this.name = productData.name; this.shortName = productData.shortName; @@ -686,6 +694,8 @@ public class LoanProductData { this.amortizationTypeOptions = amortizationTypeOptions; this.interestTypeOptions = interestTypeOptions; this.interestCalculationPeriodTypeOptions = interestCalculationPeriodTypeOptions; + this.interestRecalculationNthDayTypeOptions = interestRecalculationNthDayTypeOptions; + this.interestRecalculationDayOfWeekTypeOptions = interestRecalculationDayOfWeekTypeOptions; this.repaymentFrequencyTypeOptions = repaymentFrequencyTypeOptions; this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions; @@ -946,8 +956,11 @@ public class LoanProductData { final CalendarData compoundingCalendarData = null; return new LoanInterestRecalculationData(id, loanId, getInterestRecalculationCompoundingType(), getRescheduleStrategyType(), calendarData, getRecalculationRestFrequencyType(), getRecalculationRestFrequencyInterval(), - getRecalculationRestFrequencyDate(), compoundingCalendarData, getRecalculationCompoundingFrequencyType(), - getRecalculationCompoundingFrequencyInterval(), getRecalculationCompoundingFrequencyDate()); + getInterestRecalculationRestNthDayType(), getInterestRecalculationRestWeekDayType(), + getInterestRecalculationRestOnDayType(), compoundingCalendarData, getRecalculationCompoundingFrequencyType(), + getRecalculationCompoundingFrequencyInterval(), getInterestRecalculationCompoundingNthDayType(), + getInterestRecalculationCompoundingWeekDayType(), getInterestRecalculationCompoundingOnDayType(), + isCompoundingToBePostedAsTransaction(), allowCompoundingOnEod()); } private EnumOptionData getRescheduleStrategyType() { @@ -960,8 +973,16 @@ public class LoanProductData { return null; } - private LocalDate getRecalculationRestFrequencyDate() { - if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyDate(); } + private EnumOptionData getInterestRecalculationCompoundingNthDayType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyNthDay(); } + return null; + } + private EnumOptionData getInterestRecalculationCompoundingWeekDayType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyWeekday(); } + return null; + } + private Integer getInterestRecalculationCompoundingOnDayType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyOnDay(); } return null; } @@ -975,8 +996,18 @@ public class LoanProductData { return null; } - private LocalDate getRecalculationCompoundingFrequencyDate() { - if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyDate(); } + private EnumOptionData getInterestRecalculationRestNthDayType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyNthDay(); } + return null; + } + + private EnumOptionData getInterestRecalculationRestWeekDayType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyWeekday(); } + return null; + } + + private Integer getInterestRecalculationRestOnDayType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationRestFrequencyOnDay(); } return null; } @@ -989,6 +1020,14 @@ public class LoanProductData { if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRecalculationCompoundingFrequencyInterval(); } return null; } + public Boolean isCompoundingToBePostedAsTransaction() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.isCompoundingToBePostedAsTransaction(); } + return null; + } + public Boolean allowCompoundingOnEod() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.allowCompoundingOnEod(); } + return null; + } public boolean canDefineInstallmentAmount() { return this.canDefineInstallmentAmount; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java index 42b36838f..12dc470b2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java @@ -26,7 +26,6 @@ import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy; import org.apache.fineract.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; -import org.joda.time.LocalDate; public class LoanProductInterestRecalculationData { @@ -38,33 +37,48 @@ public class LoanProductInterestRecalculationData { private final EnumOptionData rescheduleStrategyType; private final EnumOptionData recalculationRestFrequencyType; private final Integer recalculationRestFrequencyInterval; - private final LocalDate recalculationRestFrequencyDate; + private final EnumOptionData recalculationRestFrequencyNthDay; + private final EnumOptionData recalculationRestFrequencyWeekday; + private final Integer recalculationRestFrequencyOnDay; private final EnumOptionData recalculationCompoundingFrequencyType; private final Integer recalculationCompoundingFrequencyInterval; - private final LocalDate recalculationCompoundingFrequencyDate; + private final EnumOptionData recalculationCompoundingFrequencyNthDay; + private final EnumOptionData recalculationCompoundingFrequencyWeekday; + private final Integer recalculationCompoundingFrequencyOnDay; @SuppressWarnings("unused") private final boolean isArrearsBasedOnOriginalSchedule; + private final boolean isCompoundingToBePostedAsTransaction; @SuppressWarnings("unused") private final EnumOptionData preClosureInterestCalculationStrategy; + private final boolean allowCompoundingOnEod; public LoanProductInterestRecalculationData(final Long id, final Long productId, final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType, final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval, - final LocalDate recalculationRestFrequencyDate, final EnumOptionData recalculationCompoundingFrequencyType, - final Integer recalculationCompoundingFrequencyInterval, final LocalDate recalculationCompoundingFrequencyDate, - final boolean isArrearsBasedOnOriginalSchedule, final EnumOptionData preCloseInterestCalculationStrategy) { + final EnumOptionData recalculationRestFrequencyNthDay, final EnumOptionData recalculationRestFrequencyWeekday, + final Integer recalculationRestFrequencyOnDay, final EnumOptionData recalculationCompoundingFrequencyType, + final Integer recalculationCompoundingFrequencyInterval, final EnumOptionData recalculationCompoundingFrequencyNthDay, + final EnumOptionData recalculationCompoundingFrequencyWeekday, final Integer recalculationCompoundingFrequencyOnDay, + final boolean isArrearsBasedOnOriginalSchedule, boolean isCompoundingToBePostedAsTransaction, + final EnumOptionData preCloseInterestCalculationStrategy, final boolean allowCompoundingOnEod) { this.id = id; this.productId = productId; this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; this.rescheduleStrategyType = rescheduleStrategyType; this.recalculationRestFrequencyType = recalculationRestFrequencyType; this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval; - this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; + this.recalculationRestFrequencyNthDay = recalculationRestFrequencyNthDay; + this.recalculationRestFrequencyOnDay = recalculationRestFrequencyOnDay; + this.recalculationRestFrequencyWeekday = recalculationRestFrequencyWeekday; this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType; this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval; - this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate; + this.recalculationCompoundingFrequencyNthDay = recalculationCompoundingFrequencyNthDay; + this.recalculationCompoundingFrequencyOnDay = recalculationCompoundingFrequencyOnDay; + this.recalculationCompoundingFrequencyWeekday = recalculationCompoundingFrequencyWeekday; this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule; this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy; + this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; + this.allowCompoundingOnEod = allowCompoundingOnEod; } public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() { @@ -74,16 +88,24 @@ public class LoanProductInterestRecalculationData { final EnumOptionData rescheduleStrategyType = rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT); final EnumOptionData recalculationRestFrequencyType = null; final Integer recalculationRestFrequencyInterval = null; - final LocalDate recalculationRestFrequencyDate = null; + final EnumOptionData recalculationRestFrequencyNthDay = null; + final EnumOptionData recalculationRestFrequencyWeekday = null; + final Integer recalculationRestFrequencyOnDay = null; final EnumOptionData recalculationCompoundingFrequencyType = null; final Integer recalculationCompoundingFrequencyInterval = null; - final LocalDate recalculationCompoundingFrequencyDate = null; + final EnumOptionData recalculationCompoundingFrequencyNthDay = null; + final EnumOptionData recalculationCompoundingFrequencyWeekday = null; + final Integer recalculationCompoundingFrequencyOnDay = null; final boolean isArrearsBasedOnOriginalSchedule = false; + final boolean isCompoundingToBePostedAsTransaction = false; final EnumOptionData preCloseInterestCalculationStrategy = preCloseInterestCalculationStrategy(LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE); + final boolean allowCompoundingOnEod = false; return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType, - recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyDate, - recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate, - isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy); + recalculationRestFrequencyType, recalculationRestFrequencyInterval, recalculationRestFrequencyNthDay, + recalculationRestFrequencyWeekday, recalculationRestFrequencyOnDay, recalculationCompoundingFrequencyType, + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyNthDay, + recalculationCompoundingFrequencyWeekday, recalculationCompoundingFrequencyOnDay, isArrearsBasedOnOriginalSchedule, + isCompoundingToBePostedAsTransaction, preCloseInterestCalculationStrategy, allowCompoundingOnEod); } public EnumOptionData getInterestRecalculationCompoundingType() { @@ -92,10 +114,7 @@ public class LoanProductInterestRecalculationData { public EnumOptionData getRescheduleStrategyType() { return this.rescheduleStrategyType; - } - public LocalDate getRecalculationRestFrequencyDate() { - return this.recalculationRestFrequencyDate; } public EnumOptionData getRecalculationRestFrequencyType() { @@ -114,8 +133,29 @@ public class LoanProductInterestRecalculationData { return this.recalculationCompoundingFrequencyInterval; } - public LocalDate getRecalculationCompoundingFrequencyDate() { - return this.recalculationCompoundingFrequencyDate; + public EnumOptionData getRecalculationRestFrequencyNthDay() { + return this.recalculationRestFrequencyNthDay; + } + public EnumOptionData getRecalculationRestFrequencyWeekday() { + return this.recalculationRestFrequencyWeekday; + } + public Integer getRecalculationRestFrequencyOnDay() { + return this.recalculationRestFrequencyOnDay; + } + public EnumOptionData getRecalculationCompoundingFrequencyNthDay() { + return this.recalculationCompoundingFrequencyNthDay; } + public EnumOptionData getRecalculationCompoundingFrequencyWeekday() { + return this.recalculationCompoundingFrequencyWeekday; + } + public Integer getRecalculationCompoundingFrequencyOnDay() { + return this.recalculationCompoundingFrequencyOnDay; + } + public boolean isCompoundingToBePostedAsTransaction() { + return this.isCompoundingToBePostedAsTransaction; + } + public boolean allowCompoundingOnEod() { + return this.allowCompoundingOnEod; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java index 44560547e..1b5a34faa 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java @@ -18,7 +18,6 @@ */ package org.apache.fineract.portfolio.loanproduct.domain; -import java.util.Date; import java.util.Map; import javax.persistence.Column; @@ -26,12 +25,9 @@ import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.OneToOne; import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; -import org.joda.time.LocalDate; import org.springframework.data.jpa.domain.AbstractPersistable; /** @@ -66,9 +62,14 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable @Column(name = "rest_frequency_interval", nullable = false) private Integer restInterval; - @Temporal(TemporalType.DATE) - @Column(name = "rest_freqency_date") - private Date restFrequencyDate; + @Column(name = "rest_frequency_nth_day_enum", nullable = true) + private Integer restFrequencyNthDay; + + @Column(name = "rest_frequency_weekday_enum", nullable = true) + private Integer restFrequencyWeekday; + + @Column(name = "rest_frequency_on_day", nullable = true) + private Integer restFrequencyOnDay; @Column(name = "compounding_frequency_type_enum", nullable = true) private Integer compoundingFrequencyType; @@ -76,9 +77,14 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable @Column(name = "compounding_frequency_interval", nullable = true) private Integer compoundingInterval; - @Temporal(TemporalType.DATE) - @Column(name = "compounding_freqency_date") - private Date compoundingFrequencyDate; + @Column(name = "compounding_frequency_nth_day_enum", nullable = true) + private Integer compoundingFrequencyNthDay; + + @Column(name = "compounding_frequency_weekday_enum", nullable = true) + private Integer compoundingFrequencyWeekday; + + @Column(name = "compounding_frequency_on_day", nullable = true) + private Integer compoundingFrequencyOnDay; @Column(name = "arrears_based_on_original_schedule") private boolean isArrearsBasedOnOriginalSchedule; @@ -86,6 +92,12 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable @Column(name = "pre_close_interest_calculation_strategy") private Integer preClosureInterestCalculationStrategy; + @Column(name = "is_compounding_to_be_posted_as_transaction") + private Boolean isCompoundingToBePostedAsTransaction; + + @Column(name = "allow_compounding_on_eod") + private Boolean allowCompoundingOnEod; + protected LoanProductInterestRecalculationDetails() { // } @@ -100,19 +112,16 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable final Integer recurrenceFrequency = command .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyTypeParameterName); - final LocalDate recurrenceOnLocalDate = command - .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); + final Integer recurrenceOnNthDay = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyNthDayParamName); + final Integer recurrenceOnDay = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyOnDayParamName); + final Integer recurrenceOnWeekday = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyWeekdayParamName); Integer recurrenceInterval = command .integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName); final boolean isArrearsBasedOnOriginalSchedule = command .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName); RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(recurrenceFrequency); - Date recurrenceOnDate = null; - if (recurrenceOnLocalDate != null) { - if (!frequencyType.isSameAsRepayment()) { - recurrenceOnDate = recurrenceOnLocalDate.toDate(); - } - } if (frequencyType.isSameAsRepayment()) { recurrenceInterval = 0; } @@ -121,7 +130,10 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable .fromInt(interestRecalculationCompoundingMethod); Integer compoundingRecurrenceFrequency = null; Integer compoundingInterval = null; - Date recurrenceOnCompoundingDate = null; + Integer compoundingRecurrenceOnNthDay = null; + Integer compoundingRecurrenceOnDay = null; + Integer compoundingRecurrenceOnWeekday = null; + boolean allowCompoundingOnEod = false; if (compoundingMethod.isCompoundingEnabled()) { compoundingRecurrenceFrequency = command .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName); @@ -131,14 +143,14 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable if (compoundingFrequencyType.isSameAsRepayment()) { recurrenceInterval = 0; } - final LocalDate compoundingRecurrenceOnLocalDate = command - .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName); - - if (compoundingRecurrenceOnLocalDate != null) { - if (!compoundingFrequencyType.isSameAsRepayment()) { - recurrenceOnCompoundingDate = compoundingRecurrenceOnLocalDate.toDate(); - } - } + compoundingRecurrenceOnNthDay = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName); + compoundingRecurrenceOnDay = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName); + compoundingRecurrenceOnWeekday = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName); + if (!compoundingFrequencyType.isDaily()) + allowCompoundingOnEod = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.allowCompoundingOnEodParamName); } Integer preCloseInterestCalculationStrategy = command @@ -147,25 +159,39 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable preCloseInterestCalculationStrategy = LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE.getValue(); } + final boolean isCompoundingToBePostedAsTransaction = command + .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName); + return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod, - recurrenceFrequency, recurrenceInterval, recurrenceOnDate, compoundingRecurrenceFrequency, compoundingInterval, - recurrenceOnCompoundingDate, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy); + recurrenceFrequency, recurrenceInterval, recurrenceOnNthDay, recurrenceOnDay, recurrenceOnWeekday, + compoundingRecurrenceFrequency, compoundingInterval, compoundingRecurrenceOnNthDay, compoundingRecurrenceOnDay, + compoundingRecurrenceOnWeekday, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy, + isCompoundingToBePostedAsTransaction, allowCompoundingOnEod); } private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod, final Integer restFrequencyType, final Integer restInterval, - final Date restFrequencyDate, Integer compoundingFrequencyType, Integer compoundingInterval, Date compoundingFrequencyDate, - final boolean isArrearsBasedOnOriginalSchedule, final Integer preCloseInterestCalculationStrategy) { + final Integer restFrequencyNthDay, final Integer restFrequencyOnDay, final Integer restFrequencyWeekday, + Integer compoundingFrequencyType, Integer compoundingInterval, final Integer compoundingFrequencyNthDay, + final Integer compoundingFrequencyOnDay, final Integer compoundingFrequencyWeekday, + final boolean isArrearsBasedOnOriginalSchedule, final Integer preCloseInterestCalculationStrategy, + final boolean isCompoundingToBePostedAsTransaction, final boolean allowCompoundingOnEod) { this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; this.rescheduleStrategyMethod = rescheduleStrategyMethod; this.restFrequencyType = restFrequencyType; this.restInterval = restInterval; - this.restFrequencyDate = restFrequencyDate; - this.compoundingFrequencyDate = compoundingFrequencyDate; + this.restFrequencyNthDay = restFrequencyNthDay; + this.restFrequencyOnDay = restFrequencyOnDay; + this.restFrequencyWeekday = restFrequencyWeekday; this.compoundingFrequencyType = compoundingFrequencyType; this.compoundingInterval = compoundingInterval; + this.compoundingFrequencyNthDay = compoundingFrequencyNthDay; + this.compoundingFrequencyOnDay = compoundingFrequencyOnDay; + this.compoundingFrequencyWeekday = compoundingFrequencyWeekday; this.isArrearsBasedOnOriginalSchedule = isArrearsBasedOnOriginalSchedule; this.preClosureInterestCalculationStrategy = preCloseInterestCalculationStrategy; + this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; + this.allowCompoundingOnEod = allowCompoundingOnEod; } public void updateProduct(final LoanProduct loanProduct) { @@ -209,7 +235,9 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable RecalculationFrequencyType frequencyType = RecalculationFrequencyType.fromInt(this.restFrequencyType); if (frequencyType.isSameAsRepayment()) { this.restInterval = 0; - this.restFrequencyDate = null; + this.restFrequencyNthDay = null; + this.restFrequencyWeekday = null; + this.restFrequencyOnDay = null; } else { if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyIntervalParameterName, this.restInterval)) { @@ -220,16 +248,46 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable this.restInterval = newValue; } - if (command.isChangeInLocalDateParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName, - getRestFrequencyLocalDate())) { - final LocalDate newValue = command - .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); - Date recurrenceOnDate = null; - if (newValue != null) { - recurrenceOnDate = newValue.toDate(); - } - actualChanges.put(LoanProductConstants.recalculationRestFrequencyDateParamName, newValue); - this.restFrequencyDate = recurrenceOnDate; + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyNthDayParamName, + getRestFrequencyNthDay())) { + Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyNthDayParamName); + actualChanges.put(LoanProductConstants.recalculationRestFrequencyNthDayParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.restFrequencyNthDay = newValue; + this.restFrequencyOnDay = null; + } + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyWeekdayParamName, + getRestFrequencyWeekday())) { + Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyWeekdayParamName); + actualChanges.put(LoanProductConstants.recalculationRestFrequencyWeekdayParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.restFrequencyWeekday = newValue; + this.restFrequencyOnDay = null; + } + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationRestFrequencyOnDayParamName, + getRestFrequencyOnDay())) { + Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyOnDayParamName); + actualChanges.put(LoanProductConstants.recalculationRestFrequencyOnDayParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.restFrequencyOnDay = newValue; + this.restFrequencyNthDay = null; + this.restFrequencyWeekday = null; + } + + if (frequencyType.isWeekly()) { + this.restFrequencyNthDay = null; + this.restFrequencyOnDay = null; + } else if (frequencyType.isMonthly()) { + if(command.integerValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyOnDayParamName) != null) { + this.restFrequencyNthDay = null; + this.restFrequencyWeekday = null; + } else { + this.restFrequencyOnDay = null; + } + } else if (frequencyType.isDaily()) { + this.restFrequencyNthDay = null; + this.restFrequencyWeekday = null; + this.restFrequencyOnDay = null; } } @@ -247,7 +305,9 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable RecalculationFrequencyType compoundingfrequencyType = RecalculationFrequencyType.fromInt(this.compoundingFrequencyType); if (compoundingfrequencyType.isSameAsRepayment()) { this.compoundingInterval = null; - this.compoundingFrequencyDate = null; + this.compoundingFrequencyNthDay = null; + this.compoundingFrequencyWeekday = null; + this.compoundingFrequencyOnDay = null; } else { if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, this.compoundingInterval)) { @@ -257,22 +317,66 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable 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; + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName, + getCompoundingFrequencyNthDay())) { + Integer newValue = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName); + actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.compoundingFrequencyNthDay = newValue; + this.compoundingFrequencyOnDay = null; } + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName, + getCompoundingFrequencyWeekday())) { + Integer newValue = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName); + actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.compoundingFrequencyWeekday = newValue; + this.compoundingFrequencyOnDay = null; + } + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName, + getCompoundingFrequencyOnDay())) { + Integer newValue = command + .integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName); + actualChanges.put(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.compoundingFrequencyOnDay = newValue; + this.compoundingFrequencyNthDay = null; + this.compoundingFrequencyWeekday = null; + } + + if (compoundingfrequencyType.isWeekly()) { + this.compoundingFrequencyNthDay = null; + this.compoundingFrequencyOnDay = null; + } else if (compoundingfrequencyType.isMonthly()) { + if(command.integerValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName) != null) { + this.compoundingFrequencyNthDay = null; + this.compoundingFrequencyWeekday = null; + } else { + this.compoundingFrequencyOnDay = null; + } + } else if (compoundingfrequencyType.isDaily()) { + this.compoundingFrequencyNthDay = null; + this.compoundingFrequencyWeekday = null; + this.compoundingFrequencyOnDay = null; + } + } + if (!compoundingfrequencyType.isDaily()) { + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.allowCompoundingOnEodParamName, allowCompoundingOnEod())) { + boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.allowCompoundingOnEodParamName); + actualChanges.put(LoanProductConstants.allowCompoundingOnEodParamName, newValue); + this.allowCompoundingOnEod = newValue; + } + } else { + this.allowCompoundingOnEod = false; } } else { this.compoundingFrequencyType = null; this.compoundingInterval = null; - this.compoundingFrequencyDate = null; + this.compoundingFrequencyNthDay = null; + this.compoundingFrequencyWeekday = null; + this.compoundingFrequencyOnDay = null; } if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, @@ -292,15 +396,15 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable actualChanges.put(LoanProductConstants.preClosureInterestCalculationStrategyParamName, newValue); this.preClosureInterestCalculationStrategy = newValue; } - - } - - public LocalDate getRestFrequencyLocalDate() { - LocalDate recurrenceOnLocalDate = null; - if (this.restFrequencyDate != null) { - recurrenceOnLocalDate = new LocalDate(this.restFrequencyDate); + + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, + this.isCompoundingToBePostedAsTransaction)) { + final boolean newValue = command + .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName); + actualChanges.put(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, newValue); + this.isCompoundingToBePostedAsTransaction = newValue; } - return recurrenceOnLocalDate; + } public RecalculationFrequencyType getRestFrequencyType() { @@ -311,14 +415,6 @@ 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); } @@ -334,4 +430,36 @@ public class LoanProductInterestRecalculationDetails extends AbstractPersistable public LoanPreClosureInterestCalculationStrategy preCloseInterestCalculationStrategy() { return LoanPreClosureInterestCalculationStrategy.fromInt(this.preClosureInterestCalculationStrategy); } + + public Integer getRestFrequencyNthDay() { + return this.restFrequencyNthDay; + } + + public Integer getRestFrequencyWeekday() { + return this.restFrequencyWeekday; + } + + public Integer getRestFrequencyOnDay() { + return this.restFrequencyOnDay; + } + + public Integer getCompoundingFrequencyNthDay() { + return this.compoundingFrequencyNthDay; + } + + public Integer getCompoundingFrequencyWeekday() { + return this.compoundingFrequencyWeekday; + } + + public Integer getCompoundingFrequencyOnDay() { + return this.compoundingFrequencyOnDay; + } + + public Boolean getIsCompoundingToBePostedAsTransaction() { + return this.isCompoundingToBePostedAsTransaction; + } + + public Boolean allowCompoundingOnEod() { + return this.allowCompoundingOnEod; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 1d7aaf61f..b7dbc8a9d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -346,8 +346,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche actualChanges.put(repaymentFrequencyTypeParamName, newValue); actualChanges.put("locale", localeAsInput); this.repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue); - + } if (this.repaymentPeriodFrequencyType == PeriodFrequencyType.MONTHS) { + Integer newValue = null; final String repaymentFrequencyNthDayTypeParamName = "repaymentFrequencyNthDayType"; newValue = command.integerValueOfParameterNamed(repaymentFrequencyNthDayTypeParamName); actualChanges.put(repaymentFrequencyNthDayTypeParamName, newValue); @@ -357,7 +358,6 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche actualChanges.put(repaymentFrequencyDayOfWeekTypeParamName, newValue); actualChanges.put("locale", localeAsInput); - } } final String numberOfRepaymentsParamName = "numberOfRepayments"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 3437b9466..3529323ab 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -30,13 +30,14 @@ import java.util.Map; import java.util.Set; import org.apache.commons.lang.StringUtils; -import org.apache.fineract.accounting.common.AccountingRuleType; import org.apache.fineract.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS; +import org.apache.fineract.accounting.common.AccountingRuleType; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; @@ -47,7 +48,6 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType; import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; -import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -90,10 +90,8 @@ public final class LoanProductDataValidator { LoanProductConstants.daysInYearTypeParameterName, LoanProductConstants.daysInMonthTypeParameterName, LoanProductConstants.rescheduleStrategyMethodParameterName, LoanProductConstants.interestRecalculationCompoundingMethodParameterName, - LoanProductConstants.recalculationRestFrequencyDateParamName, LoanProductConstants.recalculationRestFrequencyIntervalParameterName, LoanProductConstants.recalculationRestFrequencyTypeParameterName, - LoanProductConstants.recalculationCompoundingFrequencyDateParamName, LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, LoanProductConstants.recalculationCompoundingFrequencyTypeParameterName, LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName, @@ -104,7 +102,12 @@ public final class LoanProductDataValidator { LoanProductConstants.installmentAmountInMultiplesOfParamName, LoanProductConstants.preClosureInterestCalculationStrategyParamName, LoanProductConstants.allowAttributeOverridesParamName, LoanProductConstants.allowVariableInstallmentsParamName, LoanProductConstants.minimumGapBetweenInstallments, - LoanProductConstants.maximumGapBetweenInstallments)); + LoanProductConstants.maximumGapBetweenInstallments, LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName, + LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName, + LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName, + LoanProductConstants.recalculationRestFrequencyWeekdayParamName, + LoanProductConstants.recalculationRestFrequencyNthDayParamName, LoanProductConstants.recalculationRestFrequencyOnDayParamName, + LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName)); private final FromJsonHelper fromApiJsonHelper; @@ -786,13 +789,6 @@ public final class LoanProductDataValidator { } if (!frequencyType.isSameAsRepayment()) { - if (loanProduct == null - || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyDateParamName, element)) { - final LocalDate recurrenceOnLocalDate = this.fromApiJsonHelper.extractLocalDateNamed( - LoanProductConstants.recalculationRestFrequencyDateParamName, element); - baseDataValidator.reset().parameter(LoanProductConstants.recalculationRestFrequencyDateParamName) - .value(recurrenceOnLocalDate).notNull(); - } if (loanProduct == null || this.fromApiJsonHelper .parameterExists(LoanProductConstants.recalculationRestFrequencyIntervalParameterName, element)) { @@ -801,6 +797,20 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductConstants.recalculationRestFrequencyIntervalParameterName) .value(recurrenceInterval).notNull(); } + if (loanProduct == null + || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyNthDayParamName, element) + || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyWeekdayParamName, element)) { + CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, + LoanProductConstants.recalculationRestFrequencyNthDayParamName, + LoanProductConstants.recalculationRestFrequencyWeekdayParamName, element, this.fromApiJsonHelper); + } + if (loanProduct == null + || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationRestFrequencyOnDayParamName, element)) { + final Integer recalculationRestFrequencyOnDay = this.fromApiJsonHelper.extractIntegerNamed( + LoanProductConstants.recalculationRestFrequencyOnDayParamName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.recalculationRestFrequencyOnDayParamName) + .value(recalculationRestFrequencyOnDay).ignoreIfNull().inMinMaxRange(1, 28); + } } if (compoundingMethod.isCompoundingEnabled()) { @@ -817,11 +827,11 @@ public final class LoanProductDataValidator { compoundingfrequencyType = RecalculationFrequencyType.fromInt(recalculationCompoundingFrequencyType); if (!compoundingfrequencyType.isSameAsRepayment()) { PeriodFrequencyType repaymentFrequencyType = null; - if (loanProduct == null) { + if (this.fromApiJsonHelper.parameterExists("repaymentFrequencyType", element)) { Integer repaymentFrequencyTypeVal = this.fromApiJsonHelper.extractIntegerNamed("repaymentFrequencyType", element, Locale.getDefault()); repaymentFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyTypeVal); - } else { + } else if (loanProduct != null) { repaymentFrequencyType = loanProduct.getLoanProductRelatedDetail().getRepaymentPeriodFrequencyType(); } if (!compoundingfrequencyType.isSameFrequency(repaymentFrequencyType)) { @@ -841,14 +851,6 @@ public final class LoanProductDataValidator { } 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)) { @@ -864,6 +866,23 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName) .value(recurrenceInterval).notNull().integerInMultiplesOfNumber(repaymentEvery); } + if (loanProduct == null + || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName, + element) + || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName, + element)) { + CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, + LoanProductConstants.recalculationCompoundingFrequencyNthDayParamName, + LoanProductConstants.recalculationCompoundingFrequencyWeekdayParamName, element, this.fromApiJsonHelper); + } + if (loanProduct == null + || this.fromApiJsonHelper.parameterExists(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName, + element)) { + final Integer recalculationRestFrequencyOnDay = this.fromApiJsonHelper.extractIntegerNamed( + LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName) + .value(recalculationRestFrequencyOnDay).ignoreIfNull().inMinMaxRange(1, 28); + } } } @@ -873,6 +892,12 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductConstants.isArrearsBasedOnOriginalScheduleParamName) .value(isArrearsBasedOnOriginalSchedule).notNull().isOneOfTheseValues(true, false); } + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, element)) { + final Boolean isCompoundingToBePostedAsTransactions = this.fromApiJsonHelper.extractBooleanNamed( + LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, element); + baseDataValidator.reset().parameter(LoanProductConstants.isCompoundingToBePostedAsTransactionParamName) + .value(isCompoundingToBePostedAsTransactions).notNull().isOneOfTheseValues(true, false); + } final Integer preCloseInterestCalculationStrategy = this.fromApiJsonHelper.extractIntegerWithLocaleNamed( LoanProductConstants.preClosureInterestCalculationStrategyParamName, element); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java index 376e092f0..b0761d755 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java @@ -47,6 +47,8 @@ public interface LoanDropdownReadPlatformService { List retrieveLoanCycleValueConditionTypeOptions(); List retrieveInterestRecalculationCompoundingTypeOptions(); + List retrieveInterestRecalculationNthDayTypeOptions(); + List retrieveInterestRecalculationDayOfWeekTypeOptions(); List retrieveRescheduleStrategyTypeOptions(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java index e2d8ed5ab..b82763ff1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java @@ -31,6 +31,8 @@ import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyNthDayType; import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyType; import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType; +import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationCompoundingNthDayType; +import static org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationCompoundingDayOfWeekType; import java.util.ArrayList; import java.util.Arrays; @@ -112,7 +114,7 @@ public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlat public List retrieveRepaymentFrequencyOptionsForNthDayOfMonth() { final List repaymentFrequencyOptions = Arrays.asList(repaymentFrequencyNthDayType(NthDayType.ONE), repaymentFrequencyNthDayType(NthDayType.TWO), repaymentFrequencyNthDayType(NthDayType.THREE), - repaymentFrequencyNthDayType(NthDayType.FOUR)); + repaymentFrequencyNthDayType(NthDayType.FOUR), repaymentFrequencyNthDayType(NthDayType.LAST)); return repaymentFrequencyOptions; } @@ -138,7 +140,7 @@ public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlat public Collection retreiveTransactionProcessingStrategies() { final Collection strategyOptions = new ArrayList<>(); - Sort sort = new Sort("sortOrder") ; + Sort sort = new Sort("sortOrder"); final List strategies = this.loanTransactionProcessingStrategyRepository.findAll(sort); for (final LoanTransactionProcessingStrategy strategy : strategies) { strategyOptions.add(strategy.toData()); @@ -167,6 +169,28 @@ public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlat return interestRecalculationCompoundingTypeOptions; } + @Override + public List retrieveInterestRecalculationNthDayTypeOptions() { + final List interestRecalculationCompoundingNthDayTypeOptions = Arrays.asList( + interestRecalculationCompoundingNthDayType(NthDayType.ONE), interestRecalculationCompoundingNthDayType(NthDayType.TWO), + interestRecalculationCompoundingNthDayType(NthDayType.THREE), interestRecalculationCompoundingNthDayType(NthDayType.FOUR), + interestRecalculationCompoundingNthDayType(NthDayType.LAST)); + return interestRecalculationCompoundingNthDayTypeOptions; + } + + @Override + public List retrieveInterestRecalculationDayOfWeekTypeOptions() { + final List interestRecalculationCompoundingNthDayTypeOptions = Arrays.asList( + interestRecalculationCompoundingDayOfWeekType(DayOfWeekType.SUNDAY), + interestRecalculationCompoundingDayOfWeekType(DayOfWeekType.MONDAY), + interestRecalculationCompoundingDayOfWeekType(DayOfWeekType.TUESDAY), + interestRecalculationCompoundingDayOfWeekType(DayOfWeekType.WEDNESDAY), + interestRecalculationCompoundingDayOfWeekType(DayOfWeekType.THURSDAY), + interestRecalculationCompoundingDayOfWeekType(DayOfWeekType.FRIDAY), + interestRecalculationCompoundingDayOfWeekType(DayOfWeekType.SATURDAY)); + return interestRecalculationCompoundingNthDayTypeOptions; + } + @Override public List retrieveRescheduleStrategyTypeOptions() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java index c5466e60c..dffd39c8d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java @@ -172,6 +172,9 @@ public class LoanEnumerations { case FIVE: optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "fifth"); break; + case LAST: + optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "last"); + break; default: optionData = new EnumOptionData(new Integer(0).longValue(), codePrefix + type.getCode(), "invalid"); break; @@ -390,6 +393,10 @@ public class LoanEnumerations { optionData = new LoanTransactionEnumData(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getValue().longValue(), LoanTransactionType.REFUND_FOR_ACTIVE_LOAN.getCode(), "Refund"); break; + case INCOME_POSTING: + optionData = new LoanTransactionEnumData(LoanTransactionType.INCOME_POSTING.getValue().longValue(), + LoanTransactionType.INCOME_POSTING.getCode(), "Income Posting"); + break; default: break; } @@ -578,6 +585,48 @@ public class LoanEnumerations { } return optionData; } + public static EnumOptionData interestRecalculationCompoundingNthDayType(final Integer id) { + if (id == null) { return null; } + return interestRecalculationCompoundingNthDayType(NthDayType.fromInt(id)); + } + public static EnumOptionData interestRecalculationCompoundingNthDayType(final NthDayType type) { + final String codePrefix = "interestRecalculationCompounding."; + long nthDayValue = type.getValue().longValue(); + EnumOptionData optionData = null; + switch (type) { + case ONE: + optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "first"); + break; + case TWO: + optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "second"); + break; + case THREE: + optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "third"); + break; + case FOUR: + optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "fourth"); + break; + case FIVE: + optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "fifth"); + break; + case LAST: + optionData = new EnumOptionData(nthDayValue, codePrefix + type.getCode(), "last"); + break; + default: + optionData = new EnumOptionData(new Integer(0).longValue(), codePrefix + type.getCode(), "invalid"); + break; + } + return optionData; + } + public static EnumOptionData interestRecalculationCompoundingDayOfWeekType(final Integer id) { + if (id == null) { return null; } + return interestRecalculationCompoundingDayOfWeekType(DayOfWeekType.fromInt(id)); + } + public static EnumOptionData interestRecalculationCompoundingDayOfWeekType(final DayOfWeekType type) { + final String codePrefix = "interestRecalculationCompounding."; + EnumOptionData optionData = new EnumOptionData(type.getValue().longValue(), codePrefix + type.getCode(), type.toString()); + return optionData; + } public static EnumOptionData rescheduleStrategyType(final int id) { return rescheduleStrategyType(LoanRescheduleStrategyMethod.fromInt(id)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 47f54938b..31c79038a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -194,9 +194,16 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lpr.pre_close_interest_calculation_strategy as preCloseInterestCalculationStrategy, " + "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.rest_frequency_nth_day_enum as restFrequencyNthDayEnum, " + + "lpr.rest_frequency_weekday_enum as restFrequencyWeekDayEnum, " + + "lpr.rest_frequency_on_day as restFrequencyOnDay, " + + "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, " + + "lpr.compounding_frequency_nth_day_enum as compoundingFrequencyNthDayEnum, " + + "lpr.compounding_frequency_weekday_enum as compoundingFrequencyWeekDayEnum, " + + "lpr.compounding_frequency_on_day as compoundingFrequencyOnDay, " + + "lpr.is_compounding_to_be_posted_as_transaction as isCompoundingToBePostedAsTransaction, " + + "lpr.allow_compounding_on_eod as allowCompoundingOnEod, " + "lp.hold_guarantee_funds as holdGuaranteeFunds, " + "lp.principal_threshold_for_last_installment as principalThresholdForLastInstallment, " + "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, " @@ -361,23 +368,50 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final int restFrequencyEnumValue = JdbcSupport.getInteger(rs, "restFrequencyEnum"); final EnumOptionData restFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(restFrequencyEnumValue); final int restFrequencyInterval = JdbcSupport.getInteger(rs, "restFrequencyInterval"); - final LocalDate restFrequencyDate = JdbcSupport.getLocalDate(rs, "restFrequencyDate"); + final Integer restFrequencyNthDayEnumValue = JdbcSupport.getInteger(rs, "restFrequencyNthDayEnum"); + EnumOptionData restFrequencyNthDayEnum = null; + if (restFrequencyNthDayEnumValue != null) { + restFrequencyNthDayEnum = LoanEnumerations.interestRecalculationCompoundingNthDayType(restFrequencyNthDayEnumValue); + } + final Integer restFrequencyWeekDayEnumValue = JdbcSupport.getInteger(rs, "restFrequencyWeekDayEnum"); + EnumOptionData restFrequencyWeekDayEnum = null; + if (restFrequencyWeekDayEnumValue != null) { + restFrequencyWeekDayEnum = LoanEnumerations + .interestRecalculationCompoundingDayOfWeekType(restFrequencyWeekDayEnumValue); + } + final Integer restFrequencyOnDay = JdbcSupport.getInteger(rs, "restFrequencyOnDay"); 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 Integer compoundingFrequencyNthDayEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyNthDayEnum"); + EnumOptionData compoundingFrequencyNthDayEnum = null; + if (compoundingFrequencyNthDayEnumValue != null) { + compoundingFrequencyNthDayEnum = LoanEnumerations + .interestRecalculationCompoundingNthDayType(compoundingFrequencyNthDayEnumValue); + } + final Integer compoundingFrequencyWeekDayEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyWeekDayEnum"); + EnumOptionData compoundingFrequencyWeekDayEnum = null; + if (compoundingFrequencyWeekDayEnumValue != null) { + compoundingFrequencyWeekDayEnum = LoanEnumerations + .interestRecalculationCompoundingDayOfWeekType(compoundingFrequencyWeekDayEnumValue); + } + final Integer compoundingFrequencyOnDay = JdbcSupport.getInteger(rs, "compoundingFrequencyOnDay"); final boolean isArrearsBasedOnOriginalSchedule = rs.getBoolean("isArrearsBasedOnOriginalSchedule"); + final boolean isCompoundingToBePostedAsTransaction = rs.getBoolean("isCompoundingToBePostedAsTransaction"); final int preCloseInterestCalculationStrategyEnumValue = JdbcSupport.getInteger(rs, "preCloseInterestCalculationStrategy"); final EnumOptionData preCloseInterestCalculationStrategy = LoanEnumerations .preCloseInterestCalculationStrategy(preCloseInterestCalculationStrategyEnumValue); + final boolean allowCompoundingOnEod = rs.getBoolean("allowCompoundingOnEod"); interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType, rescheduleStrategyType, restFrequencyType, restFrequencyInterval, - restFrequencyDate, compoundingFrequencyType, compoundingInterval, compoundingFrequencyDate, - isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy); + restFrequencyNthDayEnum, restFrequencyWeekDayEnum, restFrequencyOnDay, compoundingFrequencyType, + compoundingInterval, compoundingFrequencyNthDayEnum, compoundingFrequencyWeekDayEnum, compoundingFrequencyOnDay, + isArrearsBasedOnOriginalSchedule, isCompoundingToBePostedAsTransaction, preCloseInterestCalculationStrategy, + allowCompoundingOnEod); } final boolean amortization = rs.getBoolean("amortizationBoolean"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index 2c105f4ff..9dac46094 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -342,7 +342,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl final String title = "recurring_savings_" + account.getId(); final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), - CalendarFrequencyType.from(periodFrequencyType), frequency, repeatsOnDay); + CalendarFrequencyType.from(periodFrequencyType), frequency, repeatsOnDay, null); calendarInstance = CalendarInstance.from(calendar, account.getId(), CalendarEntityType.SAVINGS.getValue()); } if (calendarInstance == null) { @@ -488,7 +488,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl accountId, CalendarEntityType.SAVINGS.getValue(), CalendarType.COLLECTION.getValue()); Calendar calendar = calendarInstance.getCalendar(); calendar.updateRepeatingCalendar(calendarStartDate, CalendarFrequencyType.from(periodFrequencyType), frequency, - repeatsOnDay); + repeatsOnDay, null); this.calendarInstanceRepository.save(calendarInstance); } diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V305__compounding_and_rest_frequency_nth_day_freq_and_insertion_script_for_accrual_job.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V305__compounding_and_rest_frequency_nth_day_freq_and_insertion_script_for_accrual_job.sql new file mode 100644 index 000000000..fba505b13 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V305__compounding_and_rest_frequency_nth_day_freq_and_insertion_script_for_accrual_job.sql @@ -0,0 +1,60 @@ +CREATE TABLE `m_loan_interest_recalculation_additional_details` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `loan_repayment_schedule_id` BIGINT NOT NULL, + `effective_date` DATE NOT NULL, + `amount` DECIMAL(19,6) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_additional_details_repayment_schedule_id` FOREIGN KEY (`loan_repayment_schedule_id`) REFERENCES `m_loan_repayment_schedule` (`id`) +); + + +ALTER TABLE `m_loan` DROP `repayment_frequency_nth_day_enum`, DROP `repayment_frequency_day_of_week_enum`; + + +ALTER TABLE `m_product_loan_recalculation_details` ADD `rest_frequency_nth_day_enum` INT(5), ADD `rest_frequency_on_day` INT(5), ADD `rest_frequency_weekday_enum` INT(5), +ADD `compounding_frequency_nth_day_enum` INT(5), ADD `compounding_frequency_on_day` INT(5), ADD `compounding_frequency_weekday_enum` INT(5), ADD `is_compounding_to_be_posted_as_transaction` TINYINT(1) NOT NULL DEFAULT '0', ADD `allow_compounding_on_eod` TINYINT(1) NOT NULL DEFAULT '0'; + + +ALTER TABLE `m_loan_recalculation_details` ADD `rest_frequency_nth_day_enum` INT(5), ADD `rest_frequency_on_day` INT(5), ADD `rest_frequency_weekday_enum` INT(5), +ADD `compounding_frequency_nth_day_enum` INT(5), ADD `compounding_frequency_on_day` INT(5), +ADD `is_compounding_to_be_posted_as_transaction` TINYINT(1) NOT NULL DEFAULT '0', +ADD `compounding_frequency_weekday_enum` INT(5), ADD `allow_compounding_on_eod` TINYINT(1) NOT NULL DEFAULT '0'; + + +UPDATE m_product_loan_recalculation_details plr SET plr.compounding_frequency_weekday_enum = (WEEKDAY(plr.compounding_freqency_date) + 1) WHERE plr.compounding_frequency_type_enum = 3 AND plr.compounding_freqency_date IS NOT NULL; + + +UPDATE m_product_loan_recalculation_details plr SET plr.compounding_frequency_on_day = DAYOFMONTH(plr.compounding_freqency_date) WHERE plr.compounding_frequency_type_enum = 4 AND plr.compounding_freqency_date IS NOT NULL; + + +UPDATE m_loan_recalculation_details lrd SET lrd.compounding_frequency_weekday_enum = (WEEKDAY(lrd.compounding_freqency_date) + 1) WHERE lrd.compounding_frequency_type_enum = 3 AND lrd.compounding_freqency_date IS NOT NULL; + + +UPDATE m_loan_recalculation_details lrd SET lrd.compounding_frequency_on_day = DAYOFMONTH(lrd.compounding_freqency_date) WHERE lrd.compounding_frequency_type_enum = 4 AND lrd.compounding_freqency_date IS NOT NULL; + + +UPDATE m_product_loan_recalculation_details plr SET plr.rest_frequency_weekday_enum = (WEEKDAY(plr.rest_freqency_date) + 1) WHERE plr.rest_frequency_type_enum = 3 AND plr.rest_freqency_date IS NOT NULL; + + +UPDATE m_product_loan_recalculation_details plr SET plr.rest_frequency_on_day = DAYOFMONTH(plr.rest_freqency_date) WHERE plr.rest_frequency_type_enum = 4 AND plr.rest_freqency_date IS NOT NULL; + + +UPDATE m_loan_recalculation_details lrd SET lrd.rest_frequency_weekday_enum = (WEEKDAY(lrd.rest_freqency_date) + 1) WHERE lrd.rest_frequency_type_enum = 3 AND lrd.rest_freqency_date IS NOT NULL; + + +UPDATE m_loan_recalculation_details lrd SET lrd.rest_frequency_on_day = DAYOFMONTH(lrd.rest_freqency_date) WHERE lrd.rest_frequency_type_enum = 4 AND lrd.rest_freqency_date IS NOT NULL; + + +ALTER TABLE `m_product_loan_recalculation_details` DROP `rest_freqency_date`, DROP `compounding_freqency_date`; + + +ALTER TABLE `m_loan_recalculation_details` DROP `rest_freqency_date`, DROP `compounding_freqency_date`; + + +ALTER TABLE `job` + CHANGE COLUMN `name` `name` VARCHAR(100) NOT NULL AFTER `id`, + CHANGE COLUMN `display_name` `display_name` VARCHAR(100) NOT NULL AFTER `name`; + +INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `group_name`, `previous_run_start_time`, `next_run_time`, `job_key`, `initializing_errorlog`, `is_active`, `currently_running`, `updates_allowed`, `scheduler_group`, `is_misfired`) VALUES ('Add Accrual Transactions For Loans With Income Posted As Transactions', 'Add Accrual Transactions For Loans With Income Posted As Transactions', '0 1 0 1/1 * ? *', now(), 5, NULL, NULL, NULL, NULL, NULL, 1, 0, 1, 3, 0); + +UPDATE `job` SET `task_priority`=6 WHERE `name`='Update Non Performing Assets'; \ No newline at end of file diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java index c8a1bba22..5e6ee294a 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanRepaymentScheduleInstallmentBuilder.java @@ -18,9 +18,12 @@ */ package org.apache.fineract.portfolio.loanaccount; +import java.util.List; + import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.joda.time.LocalDate; @@ -46,9 +49,10 @@ public class LoanRepaymentScheduleInstallmentBuilder { } public LoanRepaymentScheduleInstallment build() { + final List compoundingDetails = null; final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this.loan, this.installmentNumber, this.fromDate, this.dueDate, this.principal.getAmount(), this.interest.getAmount(), this.feeCharges.getAmount(), - this.penaltyCharges.getAmount(), this.recalculatedInterestComponent); + this.penaltyCharges.getAmount(), this.recalculatedInterestComponent, compoundingDetails); if (this.completed) { installment.payPrincipalComponent(this.latestTransactionDate, this.principal); installment.payInterestComponent(this.latestTransactionDate, this.interest);