MIFOSX-1951 : compounding changes and recalculation fixes

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