Merge branch 'PR95' into develop

This commit is contained in:
Adi Narayana Raju
2016-05-04 19:10:57 +05:30
72 changed files with 4183 additions and 3012 deletions
@@ -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));
@@ -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<HashMap>(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<HashMap>(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<HashMap> 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<HashMap>(0));
LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(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<Map<String, Object>> 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<Map<String, Object>> 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<HashMap>(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<HashMap>(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<HashMap>(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<HashMap>(0), "1", null);
LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.DEFAULT_STRATEGY, new ArrayList<HashMap>(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<HashMap> charges) {
final String compoundingStartDate = null;
return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate,
compoundingStartDate, repaymentStrategy, charges);
final String disbursementDate, final String repaymentStrategy, final List<HashMap> 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<HashMap> charges,
final String disbursementDate, final String repaymentStrategy, final List<HashMap> 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<HashMap> charges) {
return applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, disbursementDate, restStartDate,
compoundingStartDate, repaymentStrategy, charges, null, null);
final String disbursementDate, final String compoundingStartDate, final String repaymentStrategy, final List<HashMap> 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<HashMap> charges, final String graceOnInterestPayment, final String graceOnPrincipalPayment) {
final String disbursementDate, final String repaymentStrategy, final List<HashMap> 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;
}
}
@@ -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<HashMap>(0), createTranches);
LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(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<HashMap> charges, List<HashMap> tranches) {
final String disbursementDate, final String repaymentStrategy, final List<HashMap> charges, List<HashMap> 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<HashMap> charges, final String graceOnInterestPayment, final String graceOnPrincipalPayment, List<HashMap> tranches) {
final String disbursementDate, final String repaymentStrategy, final List<HashMap> charges, final String graceOnInterestPayment,
final String graceOnPrincipalPayment, List<HashMap> 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);
@@ -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<HashMap> 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)));
}
}
@@ -61,8 +61,6 @@ public class LoanApplicationTestBuilder {
private List<HashMap> disbursementData = null;
@SuppressWarnings("rawtypes")
private List<HashMap> 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;
@@ -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<String, Object> 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;
}
@@ -173,6 +173,9 @@ public class JournalEntry extends AbstractAuditableCustom<AppUser, Long> {
return this.amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public void setReversalJournalEntry(final JournalEntry reversalJournalEntry) {
this.reversalJournalEntry = reversalJournalEntry;
}
@@ -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<JournalEntry, Long
@Query("from JournalEntry journalEntry where journalEntry.entityId= :entityId and journalEntry.entityType = :entityType")
List<JournalEntry> 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<JournalEntry> findJournalEntries(@Param("transactionId") String transactionId, @Param("entityType") Integer entityType) ;
List<JournalEntry> 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);
}
@@ -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
@@ -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<GLAccount, BigDecimal> 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<GLAccount, BigDecimal> 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<ChargePaymentDTO> 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<ChargePaymentDTO> 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);
}
}
@@ -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;
@@ -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;
@@ -230,8 +230,9 @@ public class CalendarsApiResource {
final List<EnumOptionData> remindByOptions = this.dropdownReadPlatformService.retrieveCalendarRemindByOptions();
final List<EnumOptionData> frequencyOptions = this.dropdownReadPlatformService.retrieveCalendarFrequencyTypeOptions();
final List<EnumOptionData> repeatsOnDayOptions = this.dropdownReadPlatformService.retrieveCalendarWeekDaysTypeOptions();
final List<EnumOptionData> frequencyNthDayTypeOptions = this.dropdownReadPlatformService.retrieveCalendarFrequencyNthDayTypeOptions();
return CalendarData.withTemplateOptions(calendarData, entityTypeOptions, calendarTypeOptions, remindByOptions, frequencyOptions,
repeatsOnDayOptions);
repeatsOnDayOptions, frequencyNthDayTypeOptions);
}
private CommandWrapper getResourceDetails(final CalendarEntityType type, final Long entityId) {
@@ -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<EnumOptionData> entityTypeOptions;
final List<EnumOptionData> calendarTypeOptions;
final List<EnumOptionData> remindByOptions;
final List<EnumOptionData> frequencyOptions;
final List<EnumOptionData> repeatsOnDayOptions;
final List<EnumOptionData> 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<LocalDate> recurringDates = null;
final Collection<LocalDate> nextTenRecurringDates = null;
@@ -91,12 +97,14 @@ public class CalendarData {
final List<EnumOptionData> remindByOptions = null;
final List<EnumOptionData> frequencyOptions = null;
final List<EnumOptionData> repeatsOnDayOptions = null;
final List<EnumOptionData> 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<LocalDate> 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<EnumOptionData> remindByOptions = null;
final List<EnumOptionData> frequencyOptions = null;
final List<EnumOptionData> repeatsOnDayOptions = null;
final List<EnumOptionData> 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<EnumOptionData> entityTypeOptions,
final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions,
final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions) {
final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions,
final List<EnumOptionData> 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<LocalDate> recurringDates,
final Collection<LocalDate> 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<EnumOptionData> entityTypeOptions,
final EnumOptionData frequency, final Integer interval, final EnumOptionData repeatsOnDay,
final EnumOptionData repeatsOnNthDayOfMonth, final EnumOptionData remindBy, final Integer firstReminder,
final Integer secondReminder, final Collection<LocalDate> recurringDates, final Collection<LocalDate> 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<EnumOptionData> entityTypeOptions,
final List<EnumOptionData> calendarTypeOptions, final List<EnumOptionData> remindByOptions,
final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions,final LocalTime meetingTime) {
final List<EnumOptionData> repeatsOptions, final List<EnumOptionData> repeatsOnDayOptions,final LocalTime meetingTime,
final List<EnumOptionData> 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;
}
}
@@ -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<AppUser, Long> {
}
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<AppUser, Long> {
// 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<AppUser, Long> {
@SuppressWarnings("null")
public Map<String, Object> updateRepeatingCalendar(final LocalDate calendarStartDate, final CalendarFrequencyType frequencyType,
final Integer interval, final Integer repeatsOnDay) {
final Integer interval, final Integer repeatsOnDay, final Integer repeatsOnNthDay) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
if (calendarStartDate != null & this.startDate != null) {
@@ -432,7 +434,7 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
}
}
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<AppUser, Long> {
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<AppUser, Long> {
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<AppUser, Long> {
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<CalendarHistory> getCalendarHistory(){
return this.calendarHistory;
public Set<CalendarHistory> getCalendarHistory() {
return this.calendarHistory;
}
public void updateCalendarHistory(final Set<CalendarHistory> calendarHistory){
this.calendarHistory = calendarHistory;
public void updateCalendarHistory(final Set<CalendarHistory> calendarHistory) {
this.calendarHistory = calendarHistory;
}
public void setRecurrence(String recurrence) {
this.recurrence = recurrence;
}
}
@@ -31,7 +31,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_calendar_instance")
public class CalendarInstance extends AbstractPersistable<Long> {
@ManyToOne(cascade = CascadeType.ALL)
@ManyToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "calendar_id", nullable = false)
private Calendar calendar;
@@ -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);
}
}
}
@@ -33,5 +33,6 @@ public interface CalendarDropdownReadPlatformService {
List<EnumOptionData> retrieveCalendarFrequencyTypeOptions();
List<EnumOptionData> retrieveCalendarWeekDaysTypeOptions();
List<EnumOptionData> retrieveCalendarFrequencyNthDayTypeOptions();
}
@@ -55,4 +55,8 @@ public class CalendarDropdownReadPlatformServiceImpl implements CalendarDropdown
public List<EnumOptionData> retrieveCalendarWeekDaysTypeOptions() {
return CalendarEnumerations.calendarWeekDaysType(CalendarWeekDaysType.values());
}
@Override
public List<EnumOptionData> retrieveCalendarFrequencyNthDayTypeOptions() {
return CalendarEnumerations.calendarFrequencyNthDayType();
}
}
@@ -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<EnumOptionData> calendarFrequencyNthDayType() {
final List<EnumOptionData> optionDatas = Arrays.asList(calendarFrequencyNthDayType(NthDayType.ONE),
calendarFrequencyNthDayType(NthDayType.TWO), calendarFrequencyNthDayType(NthDayType.THREE),
calendarFrequencyNthDayType(NthDayType.FOUR), calendarFrequencyNthDayType(NthDayType.LAST),
calendarFrequencyNthDayType(NthDayType.ONDAY));
return optionDatas;
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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<CalendarHistory> history = calendarForUpdate.getCalendarHistory();
history.add(calendarHistory);
@@ -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);
}
}
@@ -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);
}
@@ -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";
@@ -372,6 +372,17 @@ public class LoansApiResource {
loanBasicDetails = LoanAccountData.withInterestRecalculationCalendarData(loanBasicDetails, calendarData,
compoundingCalendarData);
}
if (loanBasicDetails.isMonthlyRepaymentFrequencyType()) {
Collection<CalendarData> 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<InterestRatePeriodData> interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanId);
@@ -484,6 +495,8 @@ public class LoansApiResource {
LoanProductData product = null;
Collection<EnumOptionData> loanTermFrequencyTypeOptions = null;
Collection<EnumOptionData> repaymentFrequencyTypeOptions = null;
Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions = null;
Collection<EnumOptionData> repaymentFrequencyDayOfWeekTypeOptions = null;
Collection<TransactionProcessingStrategyData> repaymentStrategyOptions = null;
Collection<EnumOptionData> interestRateFrequencyTypeOptions = null;
Collection<EnumOptionData> 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(),
@@ -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());
}
}
@@ -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() {
@@ -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<Long> {
@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<Long> {
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<Long> {
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<Long> {
}
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<LoanTransaction> accruals, final AppUser currentUser) {
@@ -1265,12 +1268,6 @@ public class Loan extends AbstractPersistable<Long> {
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<Long> {
/**
* Update interest recalculation settings if product configuration changes
*/
public void updateLoanInterestRecalculationSettings(final LocalDate recalculationRestFrequencyDate,
final LocalDate recalculationCompoundingFrequencyDate, final JsonCommand command, final Map<String, Object> 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<Long> {
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<Holiday> 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<Long> {
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<Long> {
* 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<Long> {
}
public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> 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<Long> {
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<Long> {
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<Long> {
updateLoanSchedule(loanSchedule, currentUser);
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod());
recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod());
}
}
@@ -2633,7 +2582,8 @@ public class Loan extends AbstractPersistable<Long> {
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<Long> {
**/
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<Long> {
return installment;
}
private List<LoanTransaction> retreiveListOfIncomePostingTransactions() {
final List<LoanTransaction> 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<LoanTransaction> retreiveListOfTransactionsPostDisbursement() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
@@ -3104,8 +3066,8 @@ public class Loan extends AbstractPersistable<Long> {
final List<LoanTransaction> 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<Long> {
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<String, BigDecimal> cumulativeIncomeFromInstallments = new HashMap<>();
determineCumulativeIncomeFromInstallments(cumulativeIncomeFromInstallments);
HashMap<String, BigDecimal> 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<LoanTransaction> updatedAccrualTransactions = retreiveListOfAccrualTransactions();
LocalDate lastAccruedDate = this.getDisbursementDate();
if (updatedAccrualTransactions != null && updatedAccrualTransactions.size() > 0) {
lastAccruedDate = updatedAccrualTransactions.get(updatedAccrualTransactions.size() - 1).getTransactionDate();
}
HashMap<String, Object> 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<String, BigDecimal> 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<LoanTransaction> transactions, HashMap<String, BigDecimal> 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<Long> {
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<Long> {
}
processPostDisbursementTransactions();
processIncomeTransactions(currentUser);
}
private void updateLoanChargesPaidBy(LoanTransaction accrual, HashMap<String, Object> feeDetails,
LoanRepaymentScheduleInstallment installment) {
@SuppressWarnings("unchecked")
List<LoanCharge> loanCharges = (List<LoanCharge>) feeDetails.get("loanCharges");
@SuppressWarnings("unchecked")
List<LoanInstallmentCharge> loanInstallmentCharges = (List<LoanInstallmentCharge>) 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<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = extractInterestRecalculationAdditionalDetails();
List<LoanTransaction> incomeTransactions = retreiveListOfIncomePostingTransactions();
List<LoanTransaction> 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<LoanTransaction> 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<String, Object> 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<String, Object> feeDetails) {
BigDecimal fee = BigDecimal.ZERO;
BigDecimal penalties = BigDecimal.ZERO;
List<Integer> installments = new ArrayList<>();
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
if (loanRepaymentScheduleInstallment.getDueDate().isAfter(fromDate)
&& !loanRepaymentScheduleInstallment.getDueDate().isAfter(toDate)) {
installments.add(loanRepaymentScheduleInstallment.getInstallmentNumber());
}
}
List<LoanCharge> loanCharges = new ArrayList<>();
List<LoanInstallmentCharge> 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<LoanTransaction> transactions, LocalDate effectiveDate) {
for (LoanTransaction loanTransaction : transactions) {
if (loanTransaction.getTransactionDate().isEqual(effectiveDate)) { return loanTransaction; }
}
return null;
}
private List<LoanInterestRecalcualtionAdditionalDetails> extractInterestRecalculationAdditionalDetails() {
List<LoanInterestRecalcualtionAdditionalDetails> retDetails = new ArrayList<>();
if (null != this.repaymentScheduleInstallments && this.repaymentScheduleInstallments.size() > 0) {
Iterator<LoanRepaymentScheduleInstallment> installmentsItr = this.repaymentScheduleInstallments.iterator();
while (installmentsItr.hasNext()) {
LoanRepaymentScheduleInstallment installment = installmentsItr.next();
if (null != installment.getLoanCompoundingDetails()) {
retDetails.addAll(installment.getLoanCompoundingDetails());
}
}
}
Collections.sort(retDetails, new Comparator<LoanInterestRecalcualtionAdditionalDetails>() {
@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<Long> {
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> 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<Long> {
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<Long> {
List<LoanTermVariationsData> 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<Long> {
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<Long> {
Money penaltyCharges = Money.zero(loanCurrency());
Money totalPrincipal = Money.zero(loanCurrency());
Money totalInterest = Money.zero(loanCurrency());
final List<LoanInterestRecalcualtionAdditionalDetails> 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<Long> {
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<LoanRepaymentScheduleInstallment> fetchRepaymentScheduleInstallments() {
@@ -5141,11 +5350,17 @@ public class Loan extends AbstractPersistable<Long> {
Money outstanding = Money.zero(getCurrency());
List<LoanTransaction> 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<Long> {
@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<Long> {
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<Long> {
final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance();
final List<DisbursementData> disbursementData = getDisbursmentData();
CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
if (loanCalendar != null) {
Set<CalendarHistory> calendarHistory = loanCalendar.getCalendarHistory();
@@ -5315,11 +5538,13 @@ public class Loan extends AbstractPersistable<Long> {
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<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
@@ -5332,7 +5557,7 @@ public class Loan extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
updateLoanToLastDisbursalState(actualDisbursementDate);
for (Iterator<LoanTermVariations> 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<Long> {
}
return nextRepaymentDate;
}
public BigDecimal getDerivedAmountForCharge(LoanCharge loanCharge) {
BigDecimal amount = BigDecimal.ZERO;
if (isMultiDisburmentLoan() && (loanCharge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue())) {
@@ -288,4 +288,10 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
return amountToDeductOnThisCharge;
}
public LoanCharge getLoancharge() {
return this.loancharge;
}
public LoanRepaymentScheduleInstallment getInstallment() {
return this.installment;
}
}
@@ -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<Long> {
@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;
}
}
@@ -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<Long>
@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<Long>
@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<String, Object> 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<Long>
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<Long>
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<Long>
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;
}
}
@@ -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<LoanInterestRecalcualtionAdditionalDetails> 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<LoanInterestRecalcualtionAdditionalDetails> 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<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return this.loanCompoundingDetails;
}
}
@@ -146,6 +146,19 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
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<Long> {
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<Long> {
return !isRepayment();
}
public boolean isIncomePosting() {
return LoanTransactionType.INCOME_POSTING.equals(getTypeOf()) && isNotReversed();
}
public boolean isDisbursement() {
return LoanTransactionType.DISBURSEMENT.equals(getTypeOf()) && isNotReversed();
}
@@ -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());
}
}
@@ -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<LocalDate, Money> compoundingMap;
private final Map<LocalDate, TreeMap<LocalDate, Money>> compoundingDateVariations = new HashMap<>();
private final Map<LocalDate, Money> compoundingMap;
private final Map<LocalDate, Map<LocalDate, Money>> 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<LocalDate, Money> principalPortionMap,
final Map<LocalDate, Money> latePaymentMap, final TreeMap<LocalDate, Money> compoundingMap,
final Map<LocalDate, Money> latePaymentMap, final Map<LocalDate, Money> compoundingMap, final Money unCompoundedAmount,
final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> 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<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
final TreeMap<LocalDate, Money> compoundingMap, final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled,
final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final Map<LocalDate, Money> compoundingMap, Money unCompoundedAmount, final Map<LocalDate, Money> disburseDetailMap,
final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> 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<RecalculationDetail> recalculationDetails,
@@ -162,7 +177,7 @@ public class LoanScheduleParams {
final Money reducePrincipal = null;
final Map<LocalDate, Money> principalPortionMap = null;
final Map<LocalDate, Money> latePaymentMap = null;
final TreeMap<LocalDate, Money> compoundingMap = null;
final Map<LocalDate, Money> compoundingMap = null;
final Map<LocalDate, Money> 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<LocalDate, Money> principalPortionMap = new HashMap<>();
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Money> 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<LocalDate, Money> principalPortionMap = new HashMap<>();
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Money> 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<LocalDate, Money> getCompoundingMap() {
public Map<LocalDate, Money> getCompoundingMap() {
return this.compoundingMap;
}
@@ -434,7 +452,7 @@ public class LoanScheduleParams {
this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
}
public Map<LocalDate, TreeMap<LocalDate, Money>> getCompoundingDateVariations() {
public Map<LocalDate, Map<LocalDate, Money>> 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;
}
}
@@ -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<LoanScheduleModelPeriod> periods, final LoanScheduleParams params,
final LoanApplicationTerms loanApplicationTerms) {
final Map<LocalDate, Map<LocalDate, Money>> compoundingDetails = params.getCompoundingDateVariations();
if (compoundingDetails.isEmpty()) { return; }
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
if (loanScheduleModelPeriod.isRepaymentPeriod() && loanScheduleModelPeriod.getLoanCompoundingDetails().isEmpty()) {
Map<LocalDate, Money> periodCompoundingDetails = compoundingDetails.get(loanScheduleModelPeriod.periodFromDate());
if (periodCompoundingDetails != null) {
for (Map.Entry<LocalDate, Money> 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<LoanCharge> 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<LoanScheduleModelPeriod>) 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<LoanTransaction> 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<RecalculationDetail> transactions, final Set<LoanCharge> 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<RecalculationDetail> 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<LocalDate, Money> 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<LoanRepaymentScheduleInstallment> installments) {
boolean isCompleted = true;
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.isNotFullyPaidOff()) {
isCompleted = false;
break;
}
}
return isCompleted;
}
private Collection<RecalculationDetail> getApplicableTransactionsForPeriod(final boolean applyInterestRecalculation,
LocalDate repaymentDate, final Collection<RecalculationDetail> transactions) {
Collection<RecalculationDetail> 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<LocalDate, Money> latePaymentMap, Money outstanding) {
Map<LocalDate, Money> retainEntries = new HashMap<>();
for (Map.Entry<LocalDate, Money> 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<LocalDate, Money> principalVariationMap,
private void updateLatePaymentCompoundingAmount(final Map<LocalDate, Money> principalVariationMap,
final Map<LocalDate, Money> 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<LocalDate, Money> latePaymentMap, final LocalDate scheduledDueDate,
List<LoanRepaymentScheduleInstallment> installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate,
final TreeMap<LocalDate, Money> compoundingMap) {
List<LoanRepaymentScheduleInstallment> 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<LocalDate, Money> 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<LocalDate, Money> 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<LocalDate, Money> 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<LoanTransaction> 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<LocalDate, Money> 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<LocalDate, Money> compoundingMap, final Set<LoanCharge> charges, MonetaryCurrency currency) {
final LoanScheduleParams scheduleParams, final Set<LoanCharge> charges, MonetaryCurrency currency) {
if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
final Map<LocalDate, Money> 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<LocalDate, Money> compoundingMap) {
Map<LocalDate, Money> 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<LocalDate, Money> map, final MonetaryCurrency currency) {
Money total = Money.zero(currency);
for (Map.Entry<LocalDate, Money> 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<LoanTermVariationsData> 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<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Map<LocalDate, Money>> 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<LocalDate, Money> principalPortionMap, LoanRepaymentScheduleInstallment installment,
Collection<RecalculationDetail> 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<LocalDate, Money> principalMap,
final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations, final Money uncompoundedAmount,
final Collection<RecalculationDetail> 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<LoanInterestRecalcualtionAdditionalDetails> details = installment.getLoanCompoundingDetails();
Money totalCompounded = Money.zero(currency);
Map<LocalDate, Money> 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<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(), totalInterest.getAmount(),
feeCharges.getAmount(), penaltyCharges.getAmount(), false);
feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails);
}
/**
@@ -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<LocalDate, Money> 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,
@@ -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);
File diff suppressed because it is too large Load Diff
@@ -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<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return null;
}
}
@@ -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<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails();
}
@@ -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<LoanInterestRecalcualtionAdditionalDetails> 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<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return this.loanCompoundingDetails;
}
}
@@ -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);
@@ -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<Holiday> 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<DisbursementData> 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> 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);
}
@@ -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<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
Collection<LoanCharge> waiveLoanCharges = new ArrayList<>();
final List<LoanInterestRecalcualtionAdditionalDetails> 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);
}
@@ -46,18 +46,29 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
/**
* The parameters supported for this command.
*/
final Set<String> 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<String> 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;
@@ -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<String> 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<String> 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<ApiParameterError> 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<LoanCharge> charges, final List<ApiParameterError> 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<ApiParameterError> 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<ApiParameterError> 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) {
@@ -33,5 +33,6 @@ public interface LoanAccrualPlatformService {
void addAccrualAccounting() throws JobExecutionException;
void addPeriodicAccruals() throws JobExecutionException;
void addPeriodicAccrualsForLoansWithIncomePostedAsTransactions() throws JobExecutionException;
}
@@ -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<Long> 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()); }
}
}
}
@@ -29,4 +29,5 @@ public interface LoanAccrualWritePlatformService {
void addPeriodicAccruals(LocalDate tilldate, Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws Exception;
void addIncomeAndAccrualTransactions(Long loanId) throws Exception;
}
@@ -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<Long> existingTransactionIds = new ArrayList<>();
final List<Long> 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<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
boolean isAccountTransfer = false;
final Map<String, Object> accountingBridgeData = loan.deriveAccountingBridgeData(applicationCurrency.toData(),
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer);
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
}
}
@@ -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<LoanCollateral> 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);
}
}
}
@@ -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;
}
@@ -126,4 +126,5 @@ public interface LoanReadPlatformService {
LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(Long loanId);
Collection<Long> retrieveLoanIdsWithPendingIncomePostingTransactions();
}
@@ -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<Long> 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;
}
}
}
@@ -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<String, Object> 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<String, Object> 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<LoanInterestRecalcualtionAdditionalDetails> 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<LoanDisbursementDetails> 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<LoanRepaymentScheduleInstallment> 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);
}
}
@@ -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" ;
@@ -298,6 +298,10 @@ public class LoanProductsApiResource {
final List<EnumOptionData> rescheduleStrategyTypeOptions = dropdownReadPlatformService.retrieveRescheduleStrategyTypeOptions();
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = dropdownReadPlatformService
.retrieveInterestRecalculationFrequencyTypeOptions();
final List<EnumOptionData> interestRecalculationNthDayTypeOptions = dropdownReadPlatformService
.retrieveInterestRecalculationNthDayTypeOptions();
final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions = dropdownReadPlatformService
.retrieveInterestRecalculationDayOfWeekTypeOptions();
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService
.retrivePreCloseInterestCalculationStrategyOptions();
final List<FloatingRateData> 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);
}
}
@@ -159,6 +159,10 @@ public class LoanProductData {
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRecalculationCompoundingTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRecalculationNthDayTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> rescheduleStrategyTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> 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<EnumOptionData> valueConditionTypeOptions, final List<EnumOptionData> daysInMonthTypeOptions,
final List<EnumOptionData> daysInYearTypeOptions, final List<EnumOptionData> interestRecalculationCompoundingTypeOptions,
final List<EnumOptionData> rescheduleStrategyTypeOptions, final List<EnumOptionData> interestRecalculationFrequencyTypeOptions,
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions, final List<FloatingRateData> floatingRateOptions) {
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions, final List<FloatingRateData> floatingRateOptions,
final List<EnumOptionData> interestRecalculationNthDayTypeOptions,
final List<EnumOptionData> 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;
@@ -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;
}
}
@@ -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;
}
}
@@ -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";
@@ -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);
@@ -47,6 +47,8 @@ public interface LoanDropdownReadPlatformService {
List<EnumOptionData> retrieveLoanCycleValueConditionTypeOptions();
List<EnumOptionData> retrieveInterestRecalculationCompoundingTypeOptions();
List<EnumOptionData> retrieveInterestRecalculationNthDayTypeOptions();
List<EnumOptionData> retrieveInterestRecalculationDayOfWeekTypeOptions();
List<EnumOptionData> retrieveRescheduleStrategyTypeOptions();
@@ -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<EnumOptionData> retrieveRepaymentFrequencyOptionsForNthDayOfMonth() {
final List<EnumOptionData> 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<TransactionProcessingStrategyData> retreiveTransactionProcessingStrategies() {
final Collection<TransactionProcessingStrategyData> strategyOptions = new ArrayList<>();
Sort sort = new Sort("sortOrder") ;
Sort sort = new Sort("sortOrder");
final List<LoanTransactionProcessingStrategy> 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<EnumOptionData> retrieveInterestRecalculationNthDayTypeOptions() {
final List<EnumOptionData> interestRecalculationCompoundingNthDayTypeOptions = Arrays.asList(
interestRecalculationCompoundingNthDayType(NthDayType.ONE), interestRecalculationCompoundingNthDayType(NthDayType.TWO),
interestRecalculationCompoundingNthDayType(NthDayType.THREE), interestRecalculationCompoundingNthDayType(NthDayType.FOUR),
interestRecalculationCompoundingNthDayType(NthDayType.LAST));
return interestRecalculationCompoundingNthDayTypeOptions;
}
@Override
public List<EnumOptionData> retrieveInterestRecalculationDayOfWeekTypeOptions() {
final List<EnumOptionData> 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<EnumOptionData> retrieveRescheduleStrategyTypeOptions() {
@@ -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));
@@ -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");
@@ -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);
}
@@ -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';
@@ -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<LoanInterestRecalcualtionAdditionalDetails> 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);