Merge pull request #1571 from pramodn02/variableinstalmentchanges

MIFOSX-579:variable installment feature
This commit is contained in:
Adi Narayana Raju
2015-12-21 17:23:06 +05:30
57 changed files with 4944 additions and 1963 deletions
+645
View File
@@ -1245,6 +1245,30 @@
<td></td>
<td></td>
</tr>
<tr>
<td><a href="#loan_term_variations">Loan Term Variations</a></td>
<td>/loans/{loanId}/schedule?command=calculateLoanSchedule</td>
<td><a href="#loans_calculate_with_exceptions">Calculate Schedule with Loan Term Variations</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>/loans/{loanId}/schedule?command=addVariations</td>
<td><a href="#loans_update_variations">Create Loan Term Variations</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>/loans/{loanId}/schedule?command=deleteVariations</td>
<td><a href="#loans_delete_variations">Remove All Loan Term Variations</a></td>
<td></td>
<td></td>
<td></td>
</tr>
</table>
</div>
</div>
@@ -11765,6 +11789,603 @@ Request Body:
</div>
</div>
<!-- start Loan term variations -->
<a id="loan_term_variations" name="loan_term_variations" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h3>Loan Rescheduling</h3>
<p>Loan Term Variations provides the ability to change due dates, amounts and number of instalments before loan approval.</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>loanId</td>
</tr>
<tr>
<td class=fielddesc>The ID of the loan to be modified.</td>
</tr>
<tr class=alt>
<td>exceptions</td>
</tr>
<tr>
<td class=fielddesc>contains exception data for variations(modifiedinstallments,newinstallments and deletedinstallments described below) </td>
</tr>
</table>
<p>Exception object fields</p>
<p>Modifiedinstallments Array fields</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>dueDate</td>
</tr>
<tr>
<td class=fielddesc>Schedule dueDate for which this exception should be applied.</td>
</tr>
<tr class=alt>
<td>modifiedDueDate</td>
</tr>
<tr>
<td class=fielddesc> this is the exception data to move the due date to specified date for a instalment</td>
</tr>
<tr class=alt>
<td>installmentAmount</td>
</tr>
<tr>
<td class=fielddesc> new instalment amount(principal+interest) for the specified instalment. supported only for interest method declining with equal instalments Amortization</td>
</tr>
<tr class=alt>
<td>principal</td>
</tr>
<tr>
<td class=fielddesc> new Principal amount for the specified instalment. supported only for interest method declining with equal instalments Amortization and for Flat loans.</td>
</tr>
</table>
<p>newinstallments Array fields</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>dueDate</td>
</tr>
<tr>
<td class=fielddesc>Schedule dueDate for inserting new instalment.</td>
</tr>
<tr class=alt>
<td>installmentAmount</td>
</tr>
<tr>
<td class=fielddesc> new instalment amount(principal+interest) for the new instalment. supported only for interest method declining with equal instalments Amortization</td>
</tr>
<tr class=alt>
<td>principal</td>
</tr>
<tr>
<td class=fielddesc> new Principal amount for the new instalment. supported only for interest method declining with equal instalments Amortization and for Flat loans.</td>
</tr>
</table>
<p>deletedinstallments Array fields</p>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Field Descriptions</div></td>
</tr>
<tr class=alt>
<td>dueDate</td>
</tr>
<tr>
<td class=fielddesc>Schedule dueDate for removing specific instalment.</td>
</tr>
</table>
</div>
</div>
<a id="loans_calculate_with_exceptions" name="loans_calculate_with_exceptions" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Calculate loan repayment schedule based on Loan term variations</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>exceptions,locale,dateFormat</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/1/schedule?command=calculateLoanSchedule
</code>
<code class="method-request">
POST loans/{loanIdd}/schedule?command=calculateLoanSchedule
Content-Type: application/json
Request Body:
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"exceptions": {
"modifiedinstallments": [{
"dueDate": '01 April 2016',
"modifiedDueDate": '05 April 2016'
},
{
"dueDate": '05 January 2016',
"modifiedDueDate": '01 January 2016'
}],
"deletedinstallments": [{
"dueDate": "01 October 2016"
}],
newinstallments: [{
dueDate: '10 October 2016',
installmentAmount: '1000'
}]
}
}
</code>
<code class="method-response">
{
"currency": {
"code": "USD",
"name": "US Dollar",
"decimalPlaces": 2,
"displaySymbol": "$",
"nameCode": "currency.USD",
"displayLabel": "US Dollar ($)"
},
"loanTermInDays": 12,
"totalPrincipalDisbursed": 10000,
"totalPrincipalExpected": 10000,
"totalInterestCharged": 1340.98,
"totalFeeChargesCharged": 0,
"totalPenaltyChargesCharged": 0,
"totalRepaymentExpected": 11340.98,
"periods": [{
"period": 1,
"fromDate": [2015,
11,
1],
"dueDate": [2015,
12,
1],
"daysInPeriod": 30,
"principalOriginalDue": 745.6,
"principalDue": 745.6,
"principalOutstanding": 745.6,
"principalLoanBalanceOutstanding": 9254.4,
"interestOriginalDue": 200,
"interestDue": 200,
"interestOutstanding": 200,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 945.6,
"totalDueForPeriod": 945.6,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 945.6,
"totalOverdue": 945.6,
"totalActualCostOfLoanForPeriod": 200
},
{
"period": 2,
"fromDate": [2015,
12,
1],
"dueDate": [2016,
1,
1],
"daysInPeriod": 31,
"principalOriginalDue": 814.91,
"principalDue": 814.91,
"principalOutstanding": 814.91,
"principalLoanBalanceOutstanding": 8439.49,
"interestOriginalDue": 185.09,
"interestDue": 185.09,
"interestOutstanding": 185.09,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 1000,
"totalDueForPeriod": 1000,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 1000,
"totalActualCostOfLoanForPeriod": 185.09
},
{
"period": 3,
"fromDate": [2016,
1,
1],
"dueDate": [2016,
2,
1],
"daysInPeriod": 31,
"principalOriginalDue": 770.75,
"principalDue": 770.75,
"principalOutstanding": 770.75,
"principalLoanBalanceOutstanding": 7668.74,
"interestOriginalDue": 168.79,
"interestDue": 168.79,
"interestOutstanding": 168.79,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.54,
"totalDueForPeriod": 939.54,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.54,
"totalActualCostOfLoanForPeriod": 168.79
},
{
"period": 4,
"fromDate": [2016,
2,
1],
"dueDate": [2016,
3,
5],
"daysInPeriod": 33,
"principalOriginalDue": 786.17,
"principalDue": 786.17,
"principalOutstanding": 786.17,
"principalLoanBalanceOutstanding": 6882.57,
"interestOriginalDue": 153.37,
"interestDue": 153.37,
"interestOutstanding": 153.37,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.54,
"totalDueForPeriod": 939.54,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.54,
"totalActualCostOfLoanForPeriod": 153.37
},
{
"period": 5,
"fromDate": [2016,
3,
5],
"dueDate": [2016,
4,
5],
"daysInPeriod": 31,
"principalOriginalDue": 801.89,
"principalDue": 801.89,
"principalOutstanding": 801.89,
"principalLoanBalanceOutstanding": 6080.68,
"interestOriginalDue": 137.65,
"interestDue": 137.65,
"interestOutstanding": 137.65,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.54,
"totalDueForPeriod": 939.54,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.54,
"totalActualCostOfLoanForPeriod": 137.65
},
{
"period": 6,
"fromDate": [2016,
4,
5],
"dueDate": [2016,
5,
1],
"daysInPeriod": 26,
"principalOriginalDue": 817.93,
"principalDue": 817.93,
"principalOutstanding": 817.93,
"principalLoanBalanceOutstanding": 5262.75,
"interestOriginalDue": 121.61,
"interestDue": 121.61,
"interestOutstanding": 121.61,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.54,
"totalDueForPeriod": 939.54,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.54,
"totalActualCostOfLoanForPeriod": 121.61
},
{
"period": 7,
"fromDate": [2016,
5,
1],
"dueDate": [2016,
6,
1],
"daysInPeriod": 31,
"principalOriginalDue": 834.28,
"principalDue": 834.28,
"principalOutstanding": 834.28,
"principalLoanBalanceOutstanding": 4428.47,
"interestOriginalDue": 105.26,
"interestDue": 105.26,
"interestOutstanding": 105.26,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.54,
"totalDueForPeriod": 939.54,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.54,
"totalActualCostOfLoanForPeriod": 105.26
},
{
"period": 8,
"fromDate": [2016,
6,
1],
"dueDate": [2016,
7,
1],
"daysInPeriod": 30,
"principalOriginalDue": 850.97,
"principalDue": 850.97,
"principalOutstanding": 850.97,
"principalLoanBalanceOutstanding": 3577.5,
"interestOriginalDue": 88.57,
"interestDue": 88.57,
"interestOutstanding": 88.57,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.54,
"totalDueForPeriod": 939.54,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.54,
"totalActualCostOfLoanForPeriod": 88.57
},
{
"period": 9,
"fromDate": [2016,
7,
1],
"dueDate": [2016,
8,
1],
"daysInPeriod": 31,
"principalOriginalDue": 867.99,
"principalDue": 867.99,
"principalOutstanding": 867.99,
"principalLoanBalanceOutstanding": 2709.51,
"interestOriginalDue": 71.55,
"interestDue": 71.55,
"interestOutstanding": 71.55,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.54,
"totalDueForPeriod": 939.54,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.54,
"totalActualCostOfLoanForPeriod": 71.55
},
{
"period": 10,
"fromDate": [2016,
8,
1],
"dueDate": [2016,
9,
1],
"daysInPeriod": 31,
"principalOriginalDue": 885.35,
"principalDue": 885.35,
"principalOutstanding": 885.35,
"principalLoanBalanceOutstanding": 1824.16,
"interestOriginalDue": 54.19,
"interestDue": 54.19,
"interestOutstanding": 54.19,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.54,
"totalDueForPeriod": 939.54,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.54,
"totalActualCostOfLoanForPeriod": 54.19
},
{
"period": 11,
"fromDate": [2016,
9,
1],
"dueDate": [2016,
10,
10],
"daysInPeriod": 39,
"principalOriginalDue": 903.05,
"principalDue": 903.05,
"principalOutstanding": 903.05,
"principalLoanBalanceOutstanding": 921.11,
"interestOriginalDue": 36.48,
"interestDue": 36.48,
"interestOutstanding": 36.48,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.53,
"totalDueForPeriod": 939.53,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.53,
"totalActualCostOfLoanForPeriod": 36.48
},
{
"period": 12,
"fromDate": [2016,
10,
10],
"dueDate": [2016,
11,
1],
"daysInPeriod": 22,
"principalOriginalDue": 921.11,
"principalDue": 921.11,
"principalOutstanding": 921.11,
"principalLoanBalanceOutstanding": 0,
"interestOriginalDue": 18.42,
"interestDue": 18.42,
"interestOutstanding": 18.42,
"feeChargesDue": 0,
"penaltyChargesDue": 0,
"totalOriginalDueForPeriod": 939.53,
"totalDueForPeriod": 939.53,
"totalPaidForPeriod": 0,
"totalOutstandingForPeriod": 939.53,
"totalActualCostOfLoanForPeriod": 18.42
}]
}
</code>
</div>
</div>
<a id="loans_update_variations" name="loans_update_variations" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Updates loan repayment schedule based on Loan term variations</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td>exceptions,locale,dateFormat</td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/1/schedule?command=addVariations
</code>
<code class="method-request">
POST loans/{loanIdd}/schedule?command=addVariations
Content-Type: application/json
Request Body:
{
"locale": "en",
"dateFormat": "dd MMMM yyyy",
"exceptions": {
"modifiedinstallments": [{
"dueDate": '01 April 2016',
"modifiedDueDate": '05 April 2016'
},
{
"dueDate": '05 January 2016',
"modifiedDueDate": '01 January 2016'
}],
"deletedinstallments": [{
"dueDate": "01 October 2016"
}],
newinstallments: [{
dueDate: '10 October 2016',
installmentAmount: '1000'
}]
}
}
</code>
<code class="method-response">
{
"loanId": 1,
"changes": {
"loanTermVariations": [{
"id": 21,
"termType": {
"id": 4,
"code": "loanTermType.dueDate",
"value": "dueDate"
},
"termVariationApplicableFrom": [2016,
1,
1],
"dateValue": [2016,
1,
10],
"isSpecificToInstallment": true
},
{
"id": 22,
"termType": {
"id": 3,
"code": "loanTermType.principalAmount",
"value": "principalAmount"
},
"termVariationApplicableFrom": [2016,
1,
10],
"decimalValue": 1100,
"isSpecificToInstallment": true
},
{
"id": 23,
"termType": {
"id": 3,
"code": "loanTermType.principalAmount",
"value": "principalAmount"
},
"termVariationApplicableFrom": [2016,
8,
1],
"decimalValue": 1000,
"isSpecificToInstallment": true
},
{
"id": 24,
"termType": {
"id": 5,
"code": "loanTermType.dueDate",
"value": "insertInstallment"
},
"termVariationApplicableFrom": [2016,
8,
15],
"decimalValue": 900,
"isSpecificToInstallment": true
}]
}
}
</code>
</div>
</div>
<a id="loans_delete_variations" name="loans_delete_variations" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
<div class="method-description">
<h4>Updates loan repayment schedule by removing Loan term variations</h4>
<table class=matrixHeading>
<tr class="matrixHeadingBG">
<td><div class="mifosXHeading2">Mandatory Fields</div></td>
</tr>
<tr class=alt>
<td></td>
</tr>
</table>
</div>
<div class="method-example">
<code class="method-declaration">
POST https://DomainName/api/v1/loans/1/schedule?command=deleteVariations
</code>
<code class="method-request">
POST loans/{loanIdd}/schedule?command=deleteVariations
Content-Type: application/json
Request Body:
{}
</code>
<code class="method-response">
{
"loanId": 1,
"changes": {
"removedEntityIds": [{
21,22}]
}
}
</code>
</div>
</div>
<!-- End Loan Term variations-->
<!-- start of offices content -->
<a id="offices" name="offices" class="old-syle-anchor">&nbsp;</a>
<div class="method-section">
@@ -40862,6 +41483,30 @@ No Request Body:
<td></td>
<td></td>
</tr>
<tr>
<td><a href="#loan_term_variations">Loan Term Variations</a></td>
<td>/loans/{loanId}/schedule?command=calculateLoanSchedule</td>
<td><a href="#loans_calculate_with_exceptions">Calculate Schedule with Loan Term Variations</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>/loans/{loanId}/schedule?command=addVariations</td>
<td><a href="#loans_update_variations">Create Loan Term Variations</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>/loans/{loanId}/schedule?command=deleteVariations</td>
<td><a href="#loans_delete_variations">Remove All Loan Term Variations</a></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td><a href="#glaccounts">General Ledger Account</a></td>
<td>glaccounts</td>
@@ -3605,9 +3605,9 @@ public class ClientLoanIntegrationTest {
List<Map<String, Object>> expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2486.04", "42.87", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2486.03", "42.88", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.5", "31.41", "200", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2533.7", "19.93", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2533.71", "19.93", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
@@ -3639,9 +3639,9 @@ public class ClientLoanIntegrationTest {
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2486.04", "42.87", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2486.03", "42.88", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2497.5", "31.41", "200", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2533.7", "19.93", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2533.71", "19.93", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
Float earlyPayment = new Float("5100");
@@ -3654,7 +3654,7 @@ public class ClientLoanIntegrationTest {
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
addRepaymentValues(expectedvalues, todaysDate, -1, false, "2482.76", "46.15", "100.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "5100.0", "36.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "5100.0", "36.16", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "0", "11.16", "200", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2417.24", "11.16", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
@@ -3672,7 +3672,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.81";
String preCloseAmount = "7766.82";
testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST(
preCloseInterestStrategy, preCloseAmount);
@@ -3681,7 +3681,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.79";
String preCloseAmount = "7771.8";
testLoanScheduleWithInterestRecalculation_WITH_REST_WEEKLY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_REDUCE_NEXT_INSTALLMENTS_PRE_CLOSE_INTEREST(
preCloseInterestStrategy, preCloseAmount);
@@ -3734,9 +3734,9 @@ public class ClientLoanIntegrationTest {
List<Map<String, Object>> expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "100.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2489.31", "39.6", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2489.3", "39.61", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2500.78", "28.13", "200", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.15", "16.61", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.16", "16.62", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
@@ -3768,9 +3768,9 @@ public class ClientLoanIntegrationTest {
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance(Utils.getTimeZoneOfTenant());
addRepaymentValues(expectedvalues, todaysDate, -9, true, "2482.76", "46.15", "100.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2489.31", "39.6", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2489.3", "39.61", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2500.7", "28.21", "200", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.23", "16.61", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, false, "2527.24", "16.62", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
HashMap prepayDetail = this.loanTransactionHelper.getPrepayAmount(this.requestSpec, this.responseSpec, loanID);
@@ -19,6 +19,7 @@ import java.util.Locale;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.mifosplatform.integrationtests.common.ClientHelper;
import org.mifosplatform.integrationtests.common.GlobalConfigurationHelper;
@@ -503,6 +504,9 @@ public class SchedulerJobsTestResults {
}
// Invalid test case as it won't affect summary (Loan summary is properly
// updated before running this job)
@Ignore
@Test
public void testUpdateLoanSummaryJobOutcome() throws InterruptedException {
this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
@@ -552,7 +556,7 @@ public class SchedulerJobsTestResults {
String JobName = "Update loan Summary";
this.schedulerJobHelper.executeJob(JobName);
Float expectedSummaryAfterJob = (Float) loanSummaryBefore.get("totalExpectedRepayment")
- (Float) loanSummaryBefore.get("feeChargesPaid");
/* - (Float) loanSummaryBefore.get("feeChargesPaid")*/;
HashMap loanSummaryAfter = this.loanTransactionHelper.getLoanSummary(this.requestSpec, this.responseSpec, loanID);
Assert.assertEquals("Verifying Loan Summary after Running Update Loan Summary Scheduler Job", expectedSummaryAfterJob,
(Float) loanSummaryAfter.get("totalExpectedRepayment"));
@@ -30,8 +30,8 @@ public class CommandWrapperBuilder {
private Long templateId;
public CommandWrapper build() {
return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName, this.entityName,
this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId);
return new CommandWrapper(this.officeId, this.groupId, this.clientId, this.loanId, this.savingsId, this.actionName,
this.entityName, this.entityId, this.subentityId, this.href, this.json, this.transactionId, this.productId, this.templateId);
}
public CommandWrapperBuilder withLoanId(final Long withLoanId) {
@@ -1337,7 +1337,7 @@ public class CommandWrapperBuilder {
this.actionName = "UPDATE";
this.entityName = "CALENDAR";
this.entityId = calendarId;
this.groupId = supportedEntityId;
this.groupId = supportedEntityId;
this.href = "/" + supportedEntityType + "/" + supportedEntityId + "/calendars/" + calendarId;
return this;
}
@@ -1815,8 +1815,7 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder saveOrUpdateAttendance(final Long entityId, final String supportedEntityType,
final Long supportedEntityId) {
public CommandWrapperBuilder saveOrUpdateAttendance(final Long entityId, final String supportedEntityType, final Long supportedEntityId) {
this.actionName = "SAVEORUPDATEATTENDANCE";
this.entityName = "MEETING";
this.entityId = entityId;
@@ -2457,7 +2456,7 @@ public class CommandWrapperBuilder {
this.href = "/clients/" + clientId + "/transactions/" + transactionId + "?command=undo";
return this;
}
public CommandWrapperBuilder createProvisioningCategory() {
this.actionName = "CREATE";
this.entityName = "PROVISIONCATEGORY";
@@ -2465,7 +2464,7 @@ public class CommandWrapperBuilder {
this.href = "/provisioningcategory";
return this;
}
public CommandWrapperBuilder updateProvisioningCategory(final Long cateoryId) {
this.actionName = "UPDATE";
this.entityName = "PROVISIONCATEGORY";
@@ -2473,7 +2472,7 @@ public class CommandWrapperBuilder {
this.href = "/provisioningcategory/" + cateoryId;
return this;
}
public CommandWrapperBuilder deleteProvisioningCategory(final Long categoryId) {
this.actionName = "DELETE";
this.entityName = "PROVISIONCATEGORY";
@@ -2481,7 +2480,7 @@ public class CommandWrapperBuilder {
this.href = "/provisioningcategory/" + categoryId;
return this;
}
public CommandWrapperBuilder createProvisioningCriteria() {
this.actionName = "CREATE";
this.entityName = "PROVISIONCRITERIA";
@@ -2489,7 +2488,7 @@ public class CommandWrapperBuilder {
this.href = "/provisioningcriteria";
return this;
}
public CommandWrapperBuilder updateProvisioningCriteria(final Long criteriaId) {
this.actionName = "UPDATE";
this.entityName = "PROVISIONCRITERIA";
@@ -2497,7 +2496,7 @@ public class CommandWrapperBuilder {
this.href = "/provisioningcriteria/" + criteriaId;
return this;
}
public CommandWrapperBuilder deleteProvisioningCriteria(final Long criteriaId) {
this.actionName = "DELETE";
this.entityName = "PROVISIONCRITERIA";
@@ -2505,7 +2504,7 @@ public class CommandWrapperBuilder {
this.href = "/provisioningcriteria/" + criteriaId;
return this;
}
public CommandWrapperBuilder createProvisioningEntries() {
this.actionName = "CREATE";
this.entityName = "PROVISIONENTRIES";
@@ -2513,20 +2512,20 @@ public class CommandWrapperBuilder {
this.href = "/provisioningentries";
return this;
}
public CommandWrapperBuilder createProvisioningJournalEntries(final Long entryId) {
this.actionName = "CREATE";
this.entityName = "PROVISIONJOURNALENTRIES";
this.entityId = entryId;
this.href = "/provisioningentries/"+entryId;
this.href = "/provisioningentries/" + entryId;
return this;
}
public CommandWrapperBuilder reCreateProvisioningEntries(final Long entryId) {
this.actionName = "RECREATE";
this.entityName = "PROVISIONENTRIES";
this.entityId = entryId;
this.href = "/provisioningentries/"+entryId;
this.href = "/provisioningentries/" + entryId;
return this;
}
@@ -2542,7 +2541,25 @@ public class CommandWrapperBuilder {
this.actionName = "UPDATE";
this.entityName = "FLOATINGRATE";
this.entityId = floatingRateId;
this.href = "/floatingrates/"+floatingRateId;
this.href = "/floatingrates/" + floatingRateId;
return this;
}
public CommandWrapperBuilder createScheduleExceptions(final Long loanId) {
this.actionName = "CREATESCHEDULEEXCEPTIONS";
this.entityName = "LOAN";
this.entityId = loanId;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/schedule";
return this;
}
public CommandWrapperBuilder deleteScheduleExceptions(final Long loanId) {
this.actionName = "DELETESCHEDULEEXCEPTIONS";
this.entityName = "LOAN";
this.entityId = loanId;
this.loanId = loanId;
this.href = "/loans/" + loanId + "/schedule";
return this;
}
}
@@ -14,4 +14,6 @@ public interface CurrencyReadPlatformService {
Collection<CurrencyData> retrieveAllowedCurrencies();
Collection<CurrencyData> retrieveAllPlatformCurrencies();
CurrencyData retrieveCurrency(String code);
}
@@ -50,6 +50,14 @@ public class CurrencyReadPlatformServiceImpl implements CurrencyReadPlatformServ
return this.jdbcTemplate.query(sql, this.currencyRowMapper, new Object[] {});
}
@Override
public CurrencyData retrieveCurrency(final String code) {
final String sql = "select " + this.currencyRowMapper.schema() + " from m_currency c where c.code = ? order by c.name";
return this.jdbcTemplate.queryForObject(sql, this.currencyRowMapper, new Object[] { code });
}
private static final class CurrencyMapper implements RowMapper<CurrencyData> {
@Override
@@ -45,7 +45,42 @@ public class CalendarUtils {
public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
return getNextRecurringDate(recur, seedDate, startDate);
LocalDate nextDate = getNextRecurringDate(recur, seedDate, startDate);
nextDate = adjustDate(nextDate, seedDate, getMeetingPeriodFrequencyType(recurringRule));
return nextDate;
}
public static LocalDate adjustDate(final LocalDate date, final LocalDate seedDate, final PeriodFrequencyType frequencyType) {
LocalDate adjustedVal = date;
if (frequencyType.isMonthly() && seedDate.getDayOfMonth() > 28) {
switch (date.getMonthOfYear()) {
case 2:
if (date.year().isLeap()) {
adjustedVal = date.dayOfMonth().setCopy(29);
}
break;
case 4:
case 6:
case 9:
case 11:
if (seedDate.getDayOfMonth() > 30) {
adjustedVal = date.dayOfMonth().setCopy(30);
} else {
adjustedVal = date.dayOfMonth().setCopy(seedDate.getDayOfMonth());
}
break;
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
adjustedVal = date.dayOfMonth().setCopy(seedDate.getDayOfMonth());
break;
}
}
return adjustedVal;
}
private static LocalDate getNextRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate startDate) {
@@ -105,17 +140,18 @@ public class CalendarUtils {
final Value value = new Value(Value.DATE.getValue());
final DateList recurringDates = recur.getDates(seed, periodStart, periodEnd, value, maxCount);
return convertToLocalDateList(recurringDates);
return convertToLocalDateList(recurringDates, seedDate, getMeetingPeriodFrequencyType(recur));
}
private static Collection<LocalDate> convertToLocalDateList(final DateList dates) {
private static Collection<LocalDate> convertToLocalDateList(final DateList dates, final LocalDate seedDate,
final PeriodFrequencyType frequencyType) {
final Collection<LocalDate> recurringDates = new ArrayList<>();
for (@SuppressWarnings("rawtypes")
final Iterator iterator = dates.iterator(); iterator.hasNext();) {
final Date date = (Date) iterator.next();
recurringDates.add(new LocalDate(date));
recurringDates.add(adjustDate(new LocalDate(date), seedDate, frequencyType));
}
return recurringDates;
@@ -259,6 +295,10 @@ public class CalendarUtils {
public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return getMeetingPeriodFrequencyType(recur);
}
private static PeriodFrequencyType getMeetingPeriodFrequencyType(final Recur recur) {
PeriodFrequencyType meetingFrequencyType = PeriodFrequencyType.INVALID;
if (recur.getFrequency().equals(Recur.DAILY)) {
meetingFrequencyType = PeriodFrequencyType.DAYS;
@@ -40,10 +40,10 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
/**
* The parameters supported for this command.
*/
private final Set<String> supportedParameters = new HashSet<>(
Arrays.asList("name", "amount", "locale", "currencyCode", "currencyOptions", "chargeAppliesTo", "chargeTimeType",
"chargeCalculationType", "chargeCalculationTypeOptions", "penalty", "active", "chargePaymentMode", "feeOnMonthDay",
"feeInterval", "monthDayFormat", "minCap", "maxCap", "feeFrequency", ChargesApiConstants.glAccountIdParamName));
private final Set<String> supportedParameters = new HashSet<>(Arrays.asList("name", "amount", "locale", "currencyCode",
"currencyOptions", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "penalty",
"active", "chargePaymentMode", "feeOnMonthDay", "feeInterval", "monthDayFormat", "minCap", "maxCap", "feeFrequency",
ChargesApiConstants.glAccountIdParamName));
private final FromJsonHelper fromApiJsonHelper;
@@ -104,9 +104,9 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
.isOneOfTheseValues(ChargeCalculationType.validValuesForLoan());
}
if(chargeTimeType != null && chargeCalculationType != null){
performChargeTimeNCalculationTypeValidation(baseDataValidator, chargeTimeType, chargeCalculationType);
if (chargeTimeType != null && chargeCalculationType != null) {
performChargeTimeNCalculationTypeValidation(baseDataValidator, chargeTimeType, chargeCalculationType);
}
} else if (appliesTo.isSavingsCharge()) {
@@ -122,8 +122,8 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
if (ctt.isWeeklyFee()) {
final String monthDay = this.fromApiJsonHelper.extractStringNamed("feeOnMonthDay", element);
baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay).mustBeBlankWhenParameterProvidedIs("chargeTimeType",
chargeTimeType);
baseDataValidator.reset().parameter("feeOnMonthDay").value(monthDay)
.mustBeBlankWhenParameterProvidedIs("chargeTimeType", chargeTimeType);
}
if (ctt.isMonthlyFee()) {
@@ -305,26 +305,26 @@ public final class ChargeDefinitionCommandFromApiJsonDeserializer {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateChargeTimeNCalculationType(Integer chargeTimeType, Integer ChargeCalculationType) {
public void validateChargeTimeNCalculationType(Integer chargeTimeType, Integer ChargeCalculationType) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
performChargeTimeNCalculationTypeValidation(baseDataValidator, chargeTimeType, ChargeCalculationType);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void performChargeTimeNCalculationTypeValidation(DataValidatorBuilder baseDataValidator,
final Integer chargeTimeType, final Integer chargeCalculationType){
if (chargeTimeType == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()){
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
.isOneOfTheseValues(ChargeCalculationType.validValuesForTrancheDisbursement());
} else {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
.isNotOneOfTheseValues(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue());
}
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
private void performChargeTimeNCalculationTypeValidation(DataValidatorBuilder baseDataValidator, final Integer chargeTimeType,
final Integer chargeCalculationType) {
if (chargeTimeType.equals(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue())) {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
.isOneOfTheseValues(ChargeCalculationType.validValuesForTrancheDisbursement());
} else {
baseDataValidator.reset().parameter("chargeCalculationType").value(chargeCalculationType)
.isNotOneOfTheseValues(ChargeCalculationType.PERCENT_OF_DISBURSEMENT_AMOUNT.getValue());
}
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
}
@@ -113,7 +113,7 @@ public class ChargeEnumerations {
break;
case PERCENT_OF_AMOUNT:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT.getValue().longValue(),
ChargeCalculationType.PERCENT_OF_AMOUNT.getCode(), "% Approved Amount");
ChargeCalculationType.PERCENT_OF_AMOUNT.getCode(), "% Amount");
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
optionData = new EnumOptionData(ChargeCalculationType.PERCENT_OF_AMOUNT_AND_INTEREST.getValue().longValue(),
@@ -17,8 +17,8 @@ public interface LoanApiConstants {
public static final String disbursementIdParameterName = "id";
public static final String loanChargeIdParameterName = "loanChargeId";
public static final String principalDisbursedParameterName = "transactionAmount";
public static final String chargesParameterName = "charges" ;
public static final String chargesParameterName = "charges";
public static final String approvedLoanAmountParameterName = "approvedLoanAmount";
public static final String approvedOnDateParameterName = "approvedOnDate";
public static final String noteParameterName = "note";
@@ -33,14 +33,14 @@ public interface LoanApiConstants {
public static final String daysInMonthTypeParameterName = "daysInMonthType";
public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
// Floating interest rate related
public static final String interestRateDifferentialParameterName = "interestRateDifferential";
public static final String isFloatingInterestRateParameterName = "isFloatingInterestRate";
// Error codes
public static final String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE= "loancharge.with.calculation.type.interest.not.allowed";
public static final String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE= "loancharge.with.calculation.type.principal.not.allowed";
public static final String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE = "loancharge.with.calculation.type.interest.not.allowed";
public static final String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE = "loancharge.with.calculation.type.principal.not.allowed";
public static final String DISBURSEMENT_DATE_START_WITH_ERROR = "first.disbursement.date.must.start.with.expected.disbursement.date";
public static final String PRINCIPAL_AMOUNT_SHOULD_BE_SAME = "sum.of.multi.disburse.amounts.must.equal.with.total.principal";
public static final String DISBURSEMENT_DATE_UNIQUE_ERROR = "disbursement.date.must.be.unique.for.tranches";
@@ -48,8 +48,17 @@ public interface LoanApiConstants {
public static final String APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES = "sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal";
public static final String DISBURSEMENT_DATES_NOT_IN_ORDER = "disbursements.should.be.ordered.based.on.their.disbursement.dates";
public static final String DISBURSEMENT_DATE_BEFORE_ERROR = "disbursement.date.of.tranche.cannot.be.before.expected.disbursement.date";
public static final String isFloatingInterestRate = "isFloatingInterestRate";
public static final String interestRateDifferential = "interestRateDifferential";
public static final String interestRateDifferential = "interestRateDifferential";
public static final String exceptionParamName = "exceptions";
public static final String modifiedinstallmentsParamName = "modifiedinstallments";
public static final String newinstallmentsParamName = "newinstallments";
public static final String deletedinstallmentsParamName = "deletedinstallments";
public static final String dueDateParamName = "dueDate";
public static final String modifiedDueDateParamName = "modifiedDueDate";
public static final String principalParamName = "principal";
public static final String installmentAmountParamName = "installmentAmount";
}
@@ -0,0 +1,89 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.api;
import java.util.HashSet;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.StringUtils;
import org.mifosplatform.commands.domain.CommandWrapper;
import org.mifosplatform.commands.service.CommandWrapperBuilder;
import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService;
import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Path("/loans/{loanId}/schedule")
@Component
@Scope("singleton")
public class LoanScheduleApiResource {
private final String resourceNameForPermissions = "LOAN";
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<LoanScheduleData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final LoanScheduleCalculationPlatformService calculationPlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
@Autowired
public LoanScheduleApiResource(final PlatformSecurityContext context,
final DefaultToApiJsonSerializer<LoanScheduleData> toApiJsonSerializer,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final LoanScheduleCalculationPlatformService calculationPlatformService,
final ApiRequestParameterHelper apiRequestParameterHelper) {
this.context = context;
this.toApiJsonSerializer = toApiJsonSerializer;
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
this.calculationPlatformService = calculationPlatformService;
this.apiRequestParameterHelper = apiRequestParameterHelper;
}
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
public String calculateLoanScheduleOrSubmitVariableSchedule(@PathParam("loanId") final Long loanId,
@QueryParam("command") final String commandParam, @Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
CommandWrapper commandRequest = null;
if (is(commandParam, "calculateLoanSchedule")) {
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
final LoanScheduleData loanSchedule = this.calculationPlatformService.generateLoanScheduleForVariableInstallmentRequest(loanId,
apiRequestBodyAsJson);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return this.toApiJsonSerializer.serialize(settings, loanSchedule, new HashSet<String>());
} else if (is(commandParam, "addVariations")) {
commandRequest = new CommandWrapperBuilder().createScheduleExceptions(loanId).withJson(apiRequestBodyAsJson).build();
} else if (is(commandParam, "deleteVariations")) {
commandRequest = new CommandWrapperBuilder().deleteScheduleExceptions(loanId).build();
}
final CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
return this.toApiJsonSerializer.serialize(result);
}
private boolean is(final String commandParam, final String commandValue) {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
}
@@ -186,9 +186,14 @@ public class LoanAccountData {
// Paid In Advance
private final PaidInAdvanceData paidInAdvance;
private final Collection<InterestRatePeriodData> interestRatesPeriods;
// VariableInstallments
private final Boolean isVariableInstallmentsAllowed;
private final Integer minimumGap;
private final Integer maximumGap;
/**
* Used to produce a {@link LoanAccountData} with only collateral options
* for now.
@@ -293,14 +298,16 @@ public class LoanAccountData {
final Boolean createStandingInstructionAtDisbursement = null;
final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
final Boolean isVariableInstallmentsAllowed = Boolean.FALSE;
final Integer minimumGap = null;
final Integer maximumGap = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType,
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate,
interestRateDifferential, interestCalculationPeriodType,
loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId,
loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, totalOverpaid,
inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType,
repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId,
transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate,
interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
@@ -311,7 +318,8 @@ public class LoanAccountData {
disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap);
}
@@ -420,13 +428,17 @@ public class LoanAccountData {
final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
final Boolean isVariableInstallmentsAllowed = Boolean.FALSE;
final Integer minimumGap = null;
final Integer maximumGap = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType,
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId,
loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, totalOverpaid,
inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType,
repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId,
transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate,
interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
@@ -437,34 +449,36 @@ public class LoanAccountData {
disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap);
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, clientAcc.clientId, clientAcc.clientName,
clientAcc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate,
acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency,
acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential,
acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
clientAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions,
acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods);
clientAcc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged,
acc.interestChargedFromDate, clientAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule,
acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions,
acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions,
acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions,
acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions,
acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions,
acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter,
acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails,
acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
}
/**
@@ -573,13 +587,17 @@ public class LoanAccountData {
final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
final Boolean isVariableInstallmentsAllowed = Boolean.FALSE;
final Integer minimumGap = null;
final Integer maximumGap = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType,
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId,
loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, totalOverpaid,
inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType,
repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId,
transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate,
interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
@@ -590,34 +608,36 @@ public class LoanAccountData {
disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap);
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential,
acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
groupAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions,
acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods);
groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged,
acc.interestChargedFromDate, groupAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule,
acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions,
acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions,
acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions,
acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions,
acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions,
acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter,
acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails,
acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
}
@@ -737,14 +757,19 @@ public class LoanAccountData {
final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
final Boolean isVariableInstallmentsAllowed = Boolean.FALSE;
final Integer minimumGap = null;
final Integer maximumGap = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType,
product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), loanPurposeId,
loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(), proposedPrincipal, principal, principal,
totalOverpaid, product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, numberOfRepayments,
product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(),
product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(),
product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
product.getCurrency(), proposedPrincipal, principal, principal, totalOverpaid, product.getInArrearsTolerance(),
termFrequency, termPeriodFrequencyType, numberOfRepayments, product.getRepaymentEvery(),
product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(),
transactionProcessingStrategyName, product.getAmortizationType(), interestRatePerPeriod,
product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(),
product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(),
product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(),
product.getInterestCalculationPeriodType(), expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(),
product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary,
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
@@ -757,7 +782,9 @@ public class LoanAccountData {
product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears,
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(),
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
product.getMaximumGapBetweenInstallments());
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -794,27 +821,29 @@ public class LoanAccountData {
}
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(), product.getFundId(),
product.getFundName(), acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName,
product.getCurrency(), product.getPrincipal(), product.getPrincipal(), product.getPrincipal(), acc.totalOverpaid,
product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(),
product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(),
product.getTransactionProcessingStrategyName(), product.getAmortizationType(), product.getInterestRatePerPeriod(),
product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(),
product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(),
product.getInterestCalculationPeriodType(), acc.expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(),
product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), acc.interestChargedFromDate, acc.timeline,
acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors,
calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, null, null,
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes,
acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(),
product.canDefineInstallmentAmount(), acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations,
acc.memberVariations, product, acc.inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA,
product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(),
product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement,
paidInAdvance, acc.interestRatesPeriods);
acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(),
product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), acc.loanPurposeId,
acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, product.getCurrency(), product.getPrincipal(),
product.getPrincipal(), product.getPrincipal(), acc.totalOverpaid, product.getInArrearsTolerance(), termFrequency,
termPeriodFrequencyType, product.getNumberOfRepayments(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(),
null, null, product.getTransactionProcessingStrategyId(), product.getTransactionProcessingStrategyName(),
product.getAmortizationType(), product.getInterestRatePerPeriod(), product.getInterestRateFrequencyType(),
product.getAnnualInterestRate(), product.getInterestType(), product.isFloatingInterestRateCalculationAllowed(),
product.getDefaultDifferentialLendingRate(), product.getInterestCalculationPeriodType(), acc.expectedFirstRepaymentOnDate,
product.getGraceOnPrincipalPayment(), product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(),
acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule,
transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
repaymentFrequencyTypeOptions, null, null, repaymentStrategyOptions, interestRateFrequencyTypeOptions,
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions,
chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(), product.canDefineInstallmentAmount(),
acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product,
acc.inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(),
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance, acc.interestRatesPeriods,
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
product.getMaximumGapBetweenInstallments());
}
@@ -825,15 +854,15 @@ public class LoanAccountData {
public static LoanAccountData basicLoanDetails(final Long id, final String accountNo, final LoanStatusEnumData status,
final String externalId, final Long clientId, final String clientName, final Long clientOfficeId, final GroupGeneralData group,
final EnumOptionData loanType, final Long loanProductId, final String loanProductName, final String loanProductDescription,
final boolean isLoanProductLinkedToFloatingRate,
final Long fundId, final String fundName, final Long loanPurposeId, final String loanPurposeName, final Long loanOfficerId,
final String loanOfficerName, final CurrencyData currencyData, final BigDecimal proposedPrincipal, final BigDecimal principal,
final BigDecimal approvedPrincipal, final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance,
final Integer termFrequency, final EnumOptionData termPeriodFrequencyType, final Integer numberOfRepayments,
final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, EnumOptionData repaymentFrequencyNthDayType,
final boolean isLoanProductLinkedToFloatingRate, final Long fundId, final String fundName, final Long loanPurposeId,
final String loanPurposeName, final Long loanOfficerId, final String loanOfficerName, final CurrencyData currencyData,
final BigDecimal proposedPrincipal, final BigDecimal principal, final BigDecimal approvedPrincipal,
final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance, final Integer termFrequency,
final EnumOptionData termPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final EnumOptionData repaymentFrequencyType, EnumOptionData repaymentFrequencyNthDayType,
EnumOptionData repaymentFrequencyDayOfWeekType, final Long transactionStrategyId, final String transactionStrategyName,
final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType,
final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate,
final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType,
final LocalDate expectedFirstRepaymentOnDate, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment,
final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline,
@@ -842,7 +871,8 @@ public class LoanAccountData {
final Boolean multiDisburseLoan, Boolean canDefineInstallmentAmount, final BigDecimal fixedEmiAmont,
final BigDecimal outstandingLoanBalance, final Boolean inArrears, final Integer graceOnArrearsAgeing, final Boolean isNPA,
final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement) {
final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement,
final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap) {
final LoanScheduleData repaymentSchedule = null;
final Collection<LoanTransactionData> transactions = null;
@@ -880,23 +910,24 @@ public class LoanAccountData {
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod,
interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential,
interestCalculationPeriodType, expectedFirstRepaymentOnDate,
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary,
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions,
repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions,
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions,
chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId,
loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal,
totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery,
repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId,
transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate,
interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions,
charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions,
interestRateFrequencyTypeOptions, 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, interestRatesPeriods);
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap);
}
/*
@@ -924,28 +955,28 @@ public class LoanAccountData {
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.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential,
acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral,
guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, transactionProcessingStrategyOptions,
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, accountLinkingOptions, linkedAccount,
disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged,
acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule,
transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions,
transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes,
accountLinkingOptions, linkedAccount, disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount,
acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
@@ -957,32 +988,34 @@ public class LoanAccountData {
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance, acc.interestRatesPeriods);
acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance,
acc.interestRatesPeriods);
}
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential,
acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions,
acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods);
group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged,
acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule,
acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions,
acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions,
acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions,
acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions,
acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions,
acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter,
acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails,
acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
}
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
@@ -1024,27 +1057,28 @@ public class LoanAccountData {
}
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.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential,
acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions,
acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods);
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged,
acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule,
acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions,
acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions,
acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions,
acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions,
acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions,
acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter,
acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails,
acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
}
@@ -1054,53 +1088,55 @@ public class LoanAccountData {
final LoanInterestRecalculationData interestRecalculationData = LoanInterestRecalculationData.withCalendarData(
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.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential,
acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions,
acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods);
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged,
acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule,
acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions,
acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions,
acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions,
acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions,
acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions,
acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter,
acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails,
acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, interestRecalculationData,
acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
}
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName,
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential,
acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions,
acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions,
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions,
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount,
acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods);
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription,
acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId,
acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid,
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType,
acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType,
acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType,
acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged,
acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule,
acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions,
acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions,
acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions,
acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions,
acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions,
acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter,
acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails,
acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods,
acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap);
}
private LoanAccountData(
@@ -1164,7 +1200,8 @@ public class LoanAccountData {
final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule,
final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance,
final Collection<InterestRatePeriodData> interestRatesPeriods) {
final Collection<InterestRatePeriodData> interestRatesPeriods, final Boolean isVariableInstallmentsAllowed,
final Integer minimumGap, final Integer maximumGap) {
this.id = id;
this.accountNo = accountNo;
@@ -1332,6 +1369,9 @@ public class LoanAccountData {
this.product.setloanProductConfigurableAttributes(product.getloanProductConfigurableAttributes());
}
this.interestRatesPeriods = interestRatesPeriods;
this.isVariableInstallmentsAllowed = isVariableInstallmentsAllowed;
this.minimumGap = minimumGap;
this.maximumGap = maximumGap;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
@@ -1394,7 +1434,7 @@ public class LoanAccountData {
}
private static boolean isLoanCycleValuesWhenConditionEqual(Integer loanCycleNumber, LoanProductBorrowerCycleVariationData data) {
return data.getBorrowerCycleNumber() == loanCycleNumber && data.getValueConditionType().equals(LoanProductValueConditionType.EQUAL);
return data.getBorrowerCycleNumber().equals(loanCycleNumber) && data.getValueConditionType().equals(LoanProductValueConditionType.EQUAL);
}
public LoanProductData product() {
@@ -1431,12 +1471,12 @@ public class LoanAccountData {
}
public boolean isMultiDisburseLoan() {
return multiDisburseLoan ;
return multiDisburseLoan;
}
public BigDecimal getTotalPaidFeeCharges() {
if(this.summary != null) return this.summary.getTotalPaidFeeCharges() ;
return BigDecimal.ZERO ;
if (this.summary != null) return this.summary.getTotalPaidFeeCharges();
return BigDecimal.ZERO;
}
}
@@ -11,38 +11,42 @@ import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
public class LoanTermVariationsData {
public class LoanTermVariationsData implements Comparable<LoanTermVariationsData> {
@SuppressWarnings("unused")
private final Long id;
private final EnumOptionData termType;
private final LocalDate termVariationApplicableFrom;
private final Integer termVariationApplicableFromInstallment;
private final BigDecimal termValue;
private final BigDecimal decimalValue;
private final LocalDate dateValue;
private final boolean isSpecificToInstallment;
private Boolean isProcessed;
public LoanTermVariationsData(final Long id, final EnumOptionData termType, final LocalDate termVariationApplicableFrom,
final BigDecimal termValue) {
final BigDecimal decimalValue, final LocalDate dateValue, final boolean isSpecificToInstallment) {
this.id = id;
this.termType = termType;
this.termVariationApplicableFrom = termVariationApplicableFrom;
this.termValue = termValue;
this.termVariationApplicableFromInstallment = null;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
}
public LoanTermVariationsData(final EnumOptionData termType, final LocalDate termVariationApplicableFrom,
final BigDecimal termValue) {
final BigDecimal decimalValue, LocalDate dateValue, final boolean isSpecificToInstallment) {
this.id = null;
this.termType = termType;
this.termVariationApplicableFrom = termVariationApplicableFrom;
this.termValue = termValue;
this.termVariationApplicableFromInstallment = null;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
}
public EnumOptionData getTermType() {
return this.termType;
}
public LoanTermVariationType getTermVariationType(){
public LoanTermVariationType getTermVariationType() {
return LoanTermVariationType.fromInt(this.termType.getId().intValue());
}
@@ -50,33 +54,51 @@ public class LoanTermVariationsData {
return this.termVariationApplicableFrom;
}
public BigDecimal getTermValue() {
return this.termValue;
public BigDecimal getDecimalValue() {
return this.decimalValue;
}
public boolean isApplicable(final LocalDate fromDate, final LocalDate dueDate, int installmentNumber) {
return occursOnDayFromAndUpTo(fromDate, dueDate, this.termVariationApplicableFrom)
|| isIntegerEquals(installmentNumber, this.termVariationApplicableFromInstallment);
public boolean isApplicable(final LocalDate fromDate, final LocalDate dueDate) {
return occursOnDayFromAndUpTo(fromDate, dueDate, this.termVariationApplicableFrom);
}
private boolean occursOnDayFromAndUpTo(final LocalDate fromInclusive, final LocalDate upToNotInclusive, final LocalDate target) {
return target != null && !target.isBefore(fromInclusive) && target.isBefore(upToNotInclusive);
}
private boolean isIntegerEquals(final Integer installmentNumber, final Integer target) {
return target != null && target.equals(installmentNumber);
}
public boolean isApplicable(final LocalDate fromDate, int installmentNumber) {
return occursBefore(fromDate, this.termVariationApplicableFrom)
|| isIntegerGreterThan(installmentNumber, this.termVariationApplicableFromInstallment);
public boolean isApplicable(final LocalDate fromDate) {
return occursBefore(fromDate, this.termVariationApplicableFrom);
}
private boolean occursBefore(final LocalDate date, final LocalDate target) {
return target != null && target.isBefore(date);
}
private boolean isIntegerGreterThan(final Integer installmentNumber, final Integer target) {
return target != null && target < installmentNumber;
public LocalDate getDateValue() {
return this.dateValue;
}
public boolean isSpecificToInstallment() {
return this.isSpecificToInstallment;
}
public Boolean isProcessed() {
return this.isProcessed == null ? false : this.isProcessed;
}
public void setProcessed(Boolean isProcessed) {
this.isProcessed = isProcessed;
}
@Override
public int compareTo(LoanTermVariationsData o) {
int comparsion = getTermApplicableFrom().compareTo(o.getTermApplicableFrom());
if (comparsion == 0) {
if (o.getTermVariationType().isDueDateVariation() || o.getTermVariationType().isInsertInstallment()) {
comparsion = 1;
}
}
return comparsion;
}
}
@@ -0,0 +1,110 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.data;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import org.joda.time.LocalDate;
public class LoanTermVariationsDataWrapper {
private final List<LoanTermVariationsData> exceptionData;
private final ListIterator<LoanTermVariationsData> iterator;
private final List<LoanTermVariationsData> interestRateChanges;
private final List<LoanTermVariationsData> dueDateVariation;
private final ListIterator<LoanTermVariationsData> dueDateIterator;
public LoanTermVariationsDataWrapper(final List<LoanTermVariationsData> exceptionData) {
if (exceptionData == null) {
this.exceptionData = new ArrayList<>(1);
} else {
this.exceptionData = exceptionData;
Collections.sort(this.exceptionData);
}
this.interestRateChanges = new ArrayList<>();
this.dueDateVariation = new ArrayList<>();
for (LoanTermVariationsData loanTermVariationsData : this.exceptionData) {
if (loanTermVariationsData.getTermVariationType().isInterestRateVariation()) {
this.interestRateChanges.add(loanTermVariationsData);
} else if (loanTermVariationsData.getTermVariationType().isDueDateVariation()) {
this.dueDateVariation.add(loanTermVariationsData);
}
}
this.exceptionData.removeAll(this.interestRateChanges);
this.exceptionData.removeAll(this.dueDateVariation);
iterator = this.exceptionData.listIterator();
dueDateIterator = this.dueDateVariation.listIterator();
}
public boolean hasVariation(final LocalDate date) {
ListIterator<LoanTermVariationsData> iterator = this.iterator;
return hasNext(date, iterator);
}
private boolean hasNext(final LocalDate date, ListIterator<LoanTermVariationsData> iterator) {
boolean hasVariation = false;
if (iterator.hasNext()) {
LoanTermVariationsData loanTermVariationsData = iterator.next();
if (!loanTermVariationsData.getTermApplicableFrom().isAfter(date)) {
hasVariation = true;
}
iterator.previous();
}
return hasVariation;
}
public boolean hasDueDateVariation(final LocalDate date) {
ListIterator<LoanTermVariationsData> iterator = this.dueDateIterator;
return hasNext(date, iterator);
}
public LoanTermVariationsData nextVariation() {
return this.iterator.next();
}
public LoanTermVariationsData nextDueDateVariation() {
return this.dueDateIterator.next();
}
public List<LoanTermVariationsData> getInterestRateChanges() {
return this.interestRateChanges;
}
public List<LoanTermVariationsData> getDueDateVariation() {
return this.dueDateVariation;
}
public List<LoanTermVariationsData> getExceptionData() {
return this.exceptionData;
}
public int adjustNumberOfRepayments() {
int repaymetsForAdjust = 0;
for (LoanTermVariationsData loanTermVariations : this.exceptionData) {
if (loanTermVariations.getTermVariationType().isInsertInstallment()) {
repaymetsForAdjust++;
} else if (loanTermVariations.getTermVariationType().isDeleteInstallment()) {
repaymetsForAdjust--;
}
}
return repaymetsForAdjust;
}
public LoanTermVariationsData fetchLoanTermDueDateVariationsData(final LocalDate onDate) {
LoanTermVariationsData data = null;
for (LoanTermVariationsData termVariationsData : this.dueDateVariation) {
if (onDate.isEqual(termVariationsData.getTermApplicableFrom())) {
data = termVariationsData;
break;
}
}
return data;
}
}
@@ -345,7 +345,7 @@ public class Loan extends AbstractPersistable<Long> {
@OrderBy(value = "termApplicableFrom, id")
@LazyCollection(LazyCollectionOption.FALSE)
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true)
private final Set<LoanTermVariations> loanTermVariations = new HashSet<>();
private final List<LoanTermVariations> loanTermVariations = new ArrayList<>();
@Column(name = "total_recovered_derived", scale = 6, precision = 19)
private BigDecimal totalRecovered;
@@ -577,13 +577,9 @@ public class Loan extends AbstractPersistable<Long> {
totalChargeAmt = loanCharge.amountOutstanding();
}
} else {
chargeAmt = loanCharge.amount();
if (loanCharge.isInstalmentFee()) {
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
}
chargeAmt = loanCharge.amountOrPercentage();
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(),
totalChargeAmt);
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
// NOTE: must add new loan charge to set of loan charges before
// reporcessing the repayment schedule.
@@ -1087,14 +1083,10 @@ public class Loan extends AbstractPersistable<Long> {
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
}
} else {
chargeAmt = loanCharge.amount();
if (loanCharge.isInstalmentFee()) {
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
}
chargeAmt = loanCharge.amountOrPercentage();
}
if (charge != null)
charge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(),
totalChargeAmt);
charge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
}
@@ -1530,7 +1522,7 @@ public class Loan extends AbstractPersistable<Long> {
for (final LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
public boolean isInterestRecalculationEnabledForProduct() {
@@ -1603,14 +1595,10 @@ public class Loan extends AbstractPersistable<Long> {
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
}
} else {
chargeAmt = loanCharge.amount();
if (loanCharge.isInstalmentFee()) {
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
}
chargeAmt = loanCharge.amountOrPercentage();
}
if (loanCharge.isActive()) {
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(),
totalChargeAmt);
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
}
@@ -2340,8 +2328,10 @@ public class Loan extends AbstractPersistable<Long> {
if ((this.loanProduct.isMultiDisburseLoan() || this.loanProduct.canDefineInstallmentAmount()) && emiAmount != null
&& emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
if (this.loanProduct.isMultiDisburseLoan()) {
final Date dateValue = null;
final boolean isSpecificToInstallment = false;
LoanTermVariations loanVariationTerms = new LoanTermVariations(LoanTermVariationType.EMI_AMOUNT.getValue(),
actualDisbursementDate.toDate(), emiAmount, this);
actualDisbursementDate.toDate(), emiAmount, dateValue, isSpecificToInstallment, this);
this.loanTermVariations.add(loanVariationTerms);
} else {
this.fixedEmiAmount = emiAmount;
@@ -2580,21 +2570,6 @@ public class Loan extends AbstractPersistable<Long> {
for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
disbursementData.add(disbursementDetails.toData());
}
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<>();
boolean isDefaultEmiAmountReq = true;
for (LoanTermVariations variationTerms : this.loanTermVariations) {
if (variationTerms.getTermType().isEMIAmountVariation()) {
if (variationTerms.getTermApplicableFrom().equals(this.getDisbursementDate().toDate())) {
isDefaultEmiAmountReq = false;
}
loanVariationTermsData.add(variationTerms.toData());
}
}
if (isDefaultEmiAmountReq) {
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), this.getDisbursementDate(), this.fixedEmiAmount);
loanVariationTermsData.add(data);
}
CalendarInstance restCalendarInstance = null;
CalendarInstance compoundingCalendarInstance = null;
@@ -2612,37 +2587,46 @@ public class Loan extends AbstractPersistable<Long> {
}
BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(scheduleGeneratorDTO.getFloatingRateDTO());
LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant();
for (FloatingRatePeriodData periodData : applicableRates) {
LoanTermVariationsData loanTermVariation = new LoanTermVariationsData(
LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(),
periodData.getInterestRate());
if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) {
interestRateStartDate = periodData.getFromDateAsLocalDate();
annualNominalInterestRate = periodData.getInterestRate();
}
loanTermVariations.add(loanTermVariation);
}
}
FloatingRateDTO floatingRateDTO = scheduleGeneratorDTO.getFloatingRateDTO();
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
annualNominalInterestRate = constructLoanTermVariations(floatingRateDTO, annualNominalInterestRate, loanTermVariations);
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, getApprovedPrincipal(),
annualNominalInterestRate, loanTermVariations);
this.maxOutstandingLoanBalance, getInterestChargedFromDate(), this.loanProduct.getPrincipalThresholdForLastInstallment(),
this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod,
compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
rescheduleStrategyMethod, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
scheduleGeneratorDTO.getHolidayDetailDTO());
return loanSchedule;
}
private BigDecimal constructFloatingInterestRates(final BigDecimal annualNominalInterestRate, final FloatingRateDTO floatingRateDTO,
final List<LoanTermVariationsData> loanTermVariations) {
final LocalDate dateValue = null;
final boolean isSpecificToInstallment = false;
BigDecimal interestRate = annualNominalInterestRate;
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(floatingRateDTO);
LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant();
for (FloatingRatePeriodData periodData : applicableRates) {
LoanTermVariationsData loanTermVariation = new LoanTermVariationsData(
LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(),
periodData.getInterestRate(), dateValue, isSpecificToInstallment);
if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) {
interestRateStartDate = periodData.getFromDateAsLocalDate();
interestRate = periodData.getInterestRate();
}
loanTermVariations.add(loanTermVariation);
}
}
return interestRate;
}
private void handleDisbursementTransaction(final LocalDate disbursedOn, final LocalDateTime createdDate, final AppUser currentUser) {
// add repayment transaction to track incoming money from client to mfi
@@ -2704,8 +2688,9 @@ public class Loan extends AbstractPersistable<Long> {
getApprovedOnDate());
}
if (getExpectedFirstRepaymentOnDate() != null && disbursedOn.isAfter(getExpectedFirstRepaymentOnDate())
&& disbursedOn.toDate().equals(this.actualDisbursementDate)) {
if (getExpectedFirstRepaymentOnDate() != null
&& (disbursedOn.isAfter(this.fetchRepaymentScheduleInstallment(1).getDueDate()) || disbursedOn
.isAfter(getExpectedFirstRepaymentOnDate())) && disbursedOn.toDate().equals(this.actualDisbursementDate)) {
final String errorMessage = "submittedOnDate cannot be after the loans expectedFirstRepaymentOnDate: "
+ getExpectedFirstRepaymentOnDate().toString();
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.after.expected.first.repayment.date", errorMessage,
@@ -2815,8 +2800,13 @@ public class Loan extends AbstractPersistable<Long> {
for (final LoanRepaymentScheduleInstallment currentInstallment : this.repaymentScheduleInstallments) {
currentInstallment.resetDerivedComponents();
}
this.loanTermVariations.clear();
final List<LoanTermVariations> removeTerms = new ArrayList<>(this.loanTermVariations.size());
for (LoanTermVariations variations : this.loanTermVariations) {
if (variations.getOnLoanStatus().equals(LoanStatus.ACTIVE.getValue())) {
removeTerms.add(variations);
}
}
this.loanTermVariations.removeAll(removeTerms);
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
@@ -4602,7 +4592,7 @@ public class Loan extends AbstractPersistable<Long> {
for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isFlat()) {
amount = loanCharge.amount().divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
amount = loanCharge.amountOrPercentage();
} else {
amount = calculateInstallmentChargeAmount(loanCharge.getChargeCalculation(), loanCharge.getPercentage(), installment)
.getAmount();
@@ -5001,51 +4991,23 @@ public class Loan extends AbstractPersistable<Long> {
for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
disbursementData.add(disbursementDetails.toData());
}
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<>();
boolean isDefaultEmiAmountReq = true;
for (LoanTermVariations variationTerms : this.loanTermVariations) {
if (variationTerms.getTermType().isEMIAmountVariation()) {
if (variationTerms.getTermApplicableFrom().equals(this.getDisbursementDate().toDate())) {
isDefaultEmiAmountReq = false;
}
loanVariationTermsData.add(variationTerms.toData());
}
}
if (isDefaultEmiAmountReq) {
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), this.getDisbursementDate(), this.fixedEmiAmount);
loanVariationTermsData.add(data);
}
RecalculationFrequencyType recalculationFrequencyType = this.loanInterestRecalculationDetails().getRestFrequencyType();
RecalculationFrequencyType compoundingFrequencyType = this.loanInterestRecalculationDetails().getCompoundingFrequencyType();
BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(floatingRateDTO);
LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant();
for (FloatingRatePeriodData periodData : applicableRates) {
LoanTermVariationsData loanTermVariation = new LoanTermVariationsData(
LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(),
periodData.getInterestRate());
if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) {
interestRateStartDate = periodData.getFromDateAsLocalDate();
annualNominalInterestRate = periodData.getInterestRate();
}
loanTermVariations.add(loanTermVariation);
}
}
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
annualNominalInterestRate = constructLoanTermVariations(floatingRateDTO, annualNominalInterestRate, loanTermVariations);
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, compoundingCalendarInstance,
compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(),
this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy(),
getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations);
getInterestChargedFromDate(), this.loanInterestRecalculationDetails, calendarInstanceForInterestRecalculation,
recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType,
this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(),
this.loanProduct.preCloseInterestCalculationStrategy(), getApprovedPrincipal(), annualNominalInterestRate,
loanTermVariations);
return loanApplicationTerms;
}
@@ -5070,22 +5032,6 @@ public class Loan extends AbstractPersistable<Long> {
for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
disbursementData.add(disbursementDetails.toData());
}
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<>();
boolean isDefaultEmiAmountReq = true;
for (LoanTermVariations variationTerms : this.loanTermVariations) {
if (variationTerms.getTermType().isEMIAmountVariation()) {
if (variationTerms.getTermApplicableFrom().equals(this.getDisbursementDate().toDate())) {
isDefaultEmiAmountReq = false;
}
loanVariationTermsData.add(variationTerms.toData());
}
}
if (isDefaultEmiAmountReq) {
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), this.getDisbursementDate(),
this.fixedEmiAmount);
loanVariationTermsData.add(data);
}
RecalculationFrequencyType recalculationFrequencyType = null;
InterestRecalculationCompoundingMethod compoundingMethod = null;
@@ -5099,26 +5045,13 @@ public class Loan extends AbstractPersistable<Long> {
}
BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(floatingRateDTO);
LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant();
for (FloatingRatePeriodData periodData : applicableRates) {
LoanTermVariationsData loanTermVariation = new LoanTermVariationsData(
LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(),
periodData.getInterestRate());
if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) {
interestRateStartDate = periodData.getFromDateAsLocalDate();
annualNominalInterestRate = periodData.getInterestRate();
}
loanTermVariations.add(loanTermVariation);
}
}
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
annualNominalInterestRate = constructLoanTermVariations(floatingRateDTO, annualNominalInterestRate, loanTermVariations);
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(null, loanTermFrequency,
loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null,
getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount,
disbursementData, this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
disbursementData, this.maxOutstandingLoanBalance, getInterestChargedFromDate(),
this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(),
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance,
compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod,
@@ -5134,6 +5067,15 @@ public class Loan extends AbstractPersistable<Long> {
return installment;
}
private BigDecimal constructLoanTermVariations(FloatingRateDTO floatingRateDTO, BigDecimal annualNominalInterestRate,
List<LoanTermVariationsData> loanTermVariations) {
for (LoanTermVariations variationTerms : this.loanTermVariations) {
loanTermVariations.add(variationTerms.toData());
}
annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations);
return annualNominalInterestRate;
}
private LoanRepaymentScheduleInstallment getTotalOutstandingOnLoan() {
Money feeCharges = Money.zero(loanCurrency());
Money penaltyCharges = Money.zero(loanCurrency());
@@ -5338,11 +5280,6 @@ public class Loan extends AbstractPersistable<Long> {
final List<DisbursementData> disbursementData = getDisbursmentData();
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<LoanTermVariationsData>();
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount);
loanVariationTermsData.add(data);
RecalculationFrequencyType recalculationFrequencyType = null;
InterestRecalculationCompoundingMethod compoundingMethod = null;
RecalculationFrequencyType compoundingFrequencyType = null;
@@ -5354,30 +5291,16 @@ public class Loan extends AbstractPersistable<Long> {
rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod();
}
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(floatingRateDTO);
LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant();
for (FloatingRatePeriodData periodData : applicableRates) {
LoanTermVariationsData loanTermVariation = new LoanTermVariationsData(
LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(),
periodData.getInterestRate());
if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) {
interestRateStartDate = periodData.getFromDateAsLocalDate();
annualNominalInterestRate = periodData.getInterestRate();
}
loanTermVariations.add(loanTermVariation);
}
}
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
annualNominalInterestRate = constructFloatingInterestRates(annualNominalInterestRate, floatingRateDTO, loanTermVariations);
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, loanCalendar, getApprovedPrincipal(),
annualNominalInterestRate, loanTermVariations);
maxOutstandingBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(),
this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod,
compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations);
}
/**
@@ -5624,12 +5547,36 @@ public class Loan extends AbstractPersistable<Long> {
return this.interestRateDifferential;
}
public void setIsFloatingInterestRate(Boolean isFloatingInterestRate) {
this.isFloatingInterestRate = isFloatingInterestRate;
}
public void setIsFloatingInterestRate(Boolean isFloatingInterestRate) {
this.isFloatingInterestRate = isFloatingInterestRate;
}
public void setInterestRateDifferential(BigDecimal interestRateDifferential) {
this.interestRateDifferential = interestRateDifferential;
}
public List<LoanTermVariations> getLoanTermVariations() {
return this.loanTermVariations;
}
private int adjustNumberOfRepayments() {
int repaymetsForAdjust = 0;
for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
if (loanTermVariations.getTermType().isInsertInstallment()) {
repaymetsForAdjust++;
} else if (loanTermVariations.getTermType().isDeleteInstallment()) {
repaymetsForAdjust--;
}
}
return repaymetsForAdjust;
}
public int fetchNumberOfInstallmensAfterExceptions() {
return this.repaymentScheduleDetail().getNumberOfRepayments() + adjustNumberOfRepayments();
}
public void setExpectedFirstRepaymentOnDate(Date expectedFirstRepaymentOnDate) {
this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate;
}
public void setInterestRateDifferential(BigDecimal interestRateDifferential) {
this.interestRateDifferential = interestRateDifferential;
}
}
@@ -56,4 +56,6 @@ public interface LoanAccountDomainService {
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan);
void saveLoanWithDataIntegrityViolationChecks(Loan loan);
}
@@ -303,6 +303,28 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
"Validation errors exist.", dataValidationErrors); }
}
}
@Override
public void saveLoanWithDataIntegrityViolationChecks(final Loan loan) {
try {
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
}
}
this.loanRepository.save(loan);
} catch (final DataIntegrityViolationException e) {
final Throwable realCause = e.getCause();
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.transaction");
if (realCause.getMessage().toLowerCase().contains("external_id_unique")) {
baseDataValidator.reset().parameter("externalId").failWithCode("value.must.be.unique");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors); }
}
}
@Override
@Transactional
@@ -255,7 +255,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.amountPaid = null;
if (isInstalmentFee()) {
if (numberOfRepayments == null) {
numberOfRepayments = this.loan.repaymentScheduleDetail().getNumberOfRepayments();
numberOfRepayments = this.loan.fetchNumberOfInstallmensAfterExceptions();
}
this.amount = chargeAmount.multiply(BigDecimal.valueOf(numberOfRepayments));
} else {
@@ -366,7 +366,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
case FLAT:
if (isInstalmentFee()) {
if (numberOfRepayments == null) {
numberOfRepayments = this.loan.repaymentScheduleDetail().getNumberOfRepayments();
numberOfRepayments = this.loan.fetchNumberOfInstallmensAfterExceptions();
}
this.amount = amount.multiply(BigDecimal.valueOf(numberOfRepayments));
} else {
@@ -446,7 +446,7 @@ public class LoanCharge extends AbstractPersistable<Long> {
break;
case FLAT:
if (isInstalmentFee()) {
this.amount = newValue.multiply(BigDecimal.valueOf(this.loan.repaymentScheduleDetail().getNumberOfRepayments()));
this.amount = newValue.multiply(BigDecimal.valueOf(this.loan.fetchNumberOfInstallmensAfterExceptions()));
} else {
this.amount = newValue;
}
@@ -32,18 +32,18 @@ public class LoanRepaymentScheduleProcessingWrapper {
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, true);
currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent());
final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges,
currency, true);
currency, !period.isRecalculatedInterestComponent());
final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(),
loanCharges, currency, true);
loanCharges, currency, !period.isRecalculatedInterestComponent());
final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(),
loanCharges, currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, true);
loanCharges, currency, period, totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent());
final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(),
loanCharges, currency, true);
loanCharges, currency, !period.isRecalculatedInterestComponent());
final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate,
period.getDueDate(), loanCharges, currency, true);
period.getDueDate(), loanCharges, currency, !period.isRecalculatedInterestComponent());
period.updateChargePortion(feeChargesDueForRepaymentPeriod, feeChargesWaivedForRepaymentPeriod,
feeChargesWrittenOffForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod, penaltyChargesWaivedForRepaymentPeriod,
@@ -54,8 +54,8 @@ 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 MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, final Money totalPrincipal,
final Money totalInterest, boolean isInstallmentChargeApplicable) {
Money cumulative = Money.zero(monetaryCurrency);
@@ -75,7 +75,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
cumulative = cumulative.plus(loanCharge.amountOrPercentage());
}
} else if (loanCharge.isOverdueInstallmentCharge()
&& loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
@@ -146,7 +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 Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable) {
Money cumulative = Money.zero(currency);
@@ -166,7 +166,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
} else {
cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
cumulative = cumulative.plus(loanCharge.amountOrPercentage());
}
} else if (loanCharge.isOverdueInstallmentCharge()
&& loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
@@ -322,4 +322,9 @@ public final class LoanSummary {
public BigDecimal getTotalPenaltyChargesWaived() {
return this.totalPenaltyChargesWaived;
}
public BigDecimal getTotalExpectedRepayment() {
return this.totalExpectedRepayment;
}
}
@@ -9,7 +9,11 @@ public enum LoanTermVariationType {
INVALID(0, "loanTermType.invalid"), //
EMI_AMOUNT(1, "loanTermType.emiAmount"), //
INTEREST_RATE(2, "loanTermType.interestRate");
INTEREST_RATE(2, "loanTermType.interestRate"), //
PRINCIPAL_AMOUNT(3, "loanTermType.principalAmount"), //
DUE_DATE(4, "loanTermType.dueDate"), //
INSERT_INSTALLMENT(5, "loanTermType.insertInstallment"), //
DELETE_INSTALLMENT(6, "loanTermType.deleteInstallment");
private final Integer value;
private final String code;
@@ -37,6 +41,18 @@ public enum LoanTermVariationType {
case 2:
enumeration = LoanTermVariationType.INTEREST_RATE;
break;
case 3:
enumeration = LoanTermVariationType.PRINCIPAL_AMOUNT;
break;
case 4:
enumeration = LoanTermVariationType.DUE_DATE;
break;
case 5:
enumeration = LoanTermVariationType.INSERT_INSTALLMENT;
break;
case 6:
enumeration = LoanTermVariationType.DELETE_INSTALLMENT;
break;
}
return enumeration;
}
@@ -44,8 +60,24 @@ public enum LoanTermVariationType {
public boolean isEMIAmountVariation() {
return this.value.equals(LoanTermVariationType.EMI_AMOUNT.getValue());
}
public boolean isInterestRateVariation() {
return this.value.equals(LoanTermVariationType.INTEREST_RATE.getValue());
}
public boolean isPrincipalAmountVariation() {
return this.value.equals(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue());
}
public boolean isDueDateVariation() {
return this.value.equals(LoanTermVariationType.DUE_DATE.getValue());
}
public boolean isInsertInstallment() {
return this.value.equals(LoanTermVariationType.INSERT_INSTALLMENT.getValue());
}
public boolean isDeleteInstallment() {
return this.value.equals(LoanTermVariationType.DELETE_INSTALLMENT.getValue());
}
}
@@ -34,18 +34,31 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
private Integer termType;
@Temporal(TemporalType.DATE)
@Column(name = "applicable_from")
@Column(name = "applicable_date", nullable = false)
private Date termApplicableFrom;
@Column(name = "term_value", scale = 6, precision = 19)
private BigDecimal termValue;
@Column(name = "decimal_value", scale = 6, precision = 19)
private BigDecimal decimalValue;
public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal termValue, final Loan loan) {
@Temporal(TemporalType.DATE)
@Column(name = "date_value")
private Date dateValue;
@Column(name = "is_specific_to_installment", nullable = false)
private boolean isSpecificToInstallment;
@Column(name = "applied_on_loan_status", nullable = false)
private Integer onLoanStatus;
public LoanTermVariations(final Integer termType, final Date termApplicableFrom, final BigDecimal decimalValue, final Date dateValue,
final boolean isSpecificToInstallment, final Loan loan) {
this.loan = loan;
this.termApplicableFrom = termApplicableFrom;
this.termType = termType;
this.termValue = termValue;
this.decimalValue = decimalValue;
this.dateValue = dateValue;
this.isSpecificToInstallment = isSpecificToInstallment;
this.onLoanStatus = loan.status().getValue();
}
protected LoanTermVariations() {
@@ -58,16 +71,45 @@ public class LoanTermVariations extends AbstractPersistable<Long> {
public LoanTermVariationsData toData() {
LocalDate termStartDate = new LocalDate(this.termApplicableFrom);
LocalDate dateValue = null;
if (this.dateValue != null) {
dateValue = new LocalDate(this.dateValue);
}
EnumOptionData type = LoanEnumerations.loanvariationType(this.termType);
return new LoanTermVariationsData(getId(), type, termStartDate, this.termValue);
return new LoanTermVariationsData(getId(), type, termStartDate, this.decimalValue, dateValue, this.isSpecificToInstallment);
}
public Date getTermApplicableFrom() {
return this.termApplicableFrom;
}
public LocalDate fetchTermApplicaDate() {
return new LocalDate(this.termApplicableFrom);
}
public BigDecimal getTermValue() {
return this.termValue;
return this.decimalValue;
}
public Date getDateValue() {
return this.dateValue;
}
public LocalDate fetchDateValue() {
return this.dateValue == null ? null : new LocalDate(this.dateValue);
}
public void setTermApplicableFrom(Date termApplicableFrom) {
this.termApplicableFrom = termApplicableFrom;
}
public void setDecimalValue(BigDecimal decimalValue) {
this.decimalValue = decimalValue;
}
public Integer getOnLoanStatus() {
return this.onLoanStatus;
}
}
@@ -0,0 +1,34 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.domain;
import java.util.Comparator;
/**
* Sort loan term variations
*/
public class LoanTermVariationsComparator implements Comparator<LoanTermVariations> {
@Override
public int compare(final LoanTermVariations o1, final LoanTermVariations o2) {
int compareResult = 0;
final int comparsion = o1.getTermApplicableFrom().compareTo(o2.getTermApplicableFrom());
/**
* For Terms bearing the same effective date, we sort based on modified
* date (when available) and new inserted installment
**/
if (comparsion == 0) {
if (o2.getTermType().isDueDateVariation() || o2.getTermType().isInsertInstallment()) {
compareResult = 1;
}
} else {
compareResult = comparsion;
}
return compareResult;
}
}
@@ -172,8 +172,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
* changedTransactionDetail accordingly
**/
if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
} else{
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(newLoanTransaction
.getLoanTransactionToRepaymentScheduleMappings());
} else {
loanTransaction.reverse();
loanTransaction.updateExternalId(null);
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
@@ -124,11 +124,11 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
Guarantor guarantor = null;
for (final Guarantor avilableGuarantor : existGuarantorList) {
if (entityId != null && avilableGuarantor.getEntityId() != null && avilableGuarantor.getEntityId().equals(entityId)
&& avilableGuarantor.getGurantorType() == guarantorTypeId && avilableGuarantor.isActive()) {
&& avilableGuarantor.getGurantorType().equals(guarantorTypeId) && avilableGuarantor.isActive()) {
if (guarantorCommand.getSavingsId() == null || avilableGuarantor.hasGuarantor(guarantorCommand.getSavingsId())) {
/** Get the right guarantor based on guarantorType **/
String defaultUserMessage = null;
if (guarantorTypeId == GuarantorType.STAFF.getValue()) {
if (guarantorTypeId.equals(GuarantorType.STAFF.getValue())) {
defaultUserMessage = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId).displayName();
} else {
defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
@@ -196,7 +196,7 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
if (guarantorType.isCustomer() || guarantorType.isStaff()) {
final Long entityId = guarantorCommand.getEntityId();
for (final Guarantor guarantor : existGuarantorList) {
if (guarantor.getEntityId() == entityId && guarantor.getGurantorType() == guarantorTypeId
if (guarantor.getEntityId().equals(entityId) && guarantor.getGurantorType().equals(guarantorTypeId)
&& !guarantorForUpdate.getId().equals(guarantor.getId())) {
String defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
defaultUserMessage = defaultUserMessage + " is already exist as a guarantor for this loan";
@@ -0,0 +1,32 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.handler;
import org.mifosplatform.commands.annotation.CommandType;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@CommandType(entity = "LOAN", action = "CREATESCHEDULEEXCEPTIONS")
public class LoanScheduleCreateVariationCommandHandler implements NewCommandSourceHandler {
private final LoanScheduleWritePlatformService loanScheduleWritePlatformService;
@Autowired
public LoanScheduleCreateVariationCommandHandler(final LoanScheduleWritePlatformService loanScheduleWritePlatformService) {
this.loanScheduleWritePlatformService = loanScheduleWritePlatformService;
}
@Override
public CommandProcessingResult processCommand(JsonCommand command) {
return this.loanScheduleWritePlatformService.addLoanScheduleVariations(command.getLoanId(), command);
}
}
@@ -0,0 +1,32 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.handler;
import org.mifosplatform.commands.annotation.CommandType;
import org.mifosplatform.commands.handler.NewCommandSourceHandler;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleWritePlatformService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@CommandType(entity = "LOAN", action = "DELETESCHEDULEEXCEPTIONS")
public class LoanScheduleDeleteVariationCommandHandler implements NewCommandSourceHandler {
private final LoanScheduleWritePlatformService loanScheduleWritePlatformService;
@Autowired
public LoanScheduleDeleteVariationCommandHandler(final LoanScheduleWritePlatformService loanScheduleWritePlatformService) {
this.loanScheduleWritePlatformService = loanScheduleWritePlatformService;
}
@Override
public CommandProcessingResult processCommand(JsonCommand command) {
return this.loanScheduleWritePlatformService.deleteLoanScheduleVariations(command.getLoanId());
}
}
@@ -0,0 +1,436 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.loanschedule.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.RecalculationDetail;
public class LoanScheduleParams {
// Actual period Number as per the schedule
private int periodNumber;
// Actual period Number plus interest only repayments
private int instalmentNumber;
private LocalDate periodStartDate;
private LocalDate actualRepaymentDate;
// variables for cumulative totals
private Money totalCumulativePrincipal;
private Money totalCumulativeInterest;
private Money totalFeeChargesCharged;
private Money totalPenaltyChargesCharged;
private Money totalRepaymentExpected;
private Money totalOutstandingInterestPaymentDueToGrace;
// early payments will be added here and as per the selected strategy
// action will be performed on this value
private Money reducePrincipal;
// principal changes will be added along with date(after applying rest)
// from when these amounts will effect the outstanding balance for
// interest calculation
private final Map<LocalDate, Money> principalPortionMap;
// 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
private final Map<LocalDate, Money> latePaymentMap;
// 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
private final TreeMap<LocalDate, Money> compoundingMap;
private final Map<LocalDate, TreeMap<LocalDate, Money>> compoundingDateVariations = new HashMap<>();
// disbursement map for tranche details(will added to outstanding
// balance as per the start date)
private final Map<LocalDate, Money> disburseDetailMap;
private Money principalToBeScheduled;
private Money outstandingBalance;
// total outstanding balance as per rest for interest calculation.
private Money outstandingBalanceAsPerRest;
private final List<LoanRepaymentScheduleInstallment> installments;
private final Collection<RecalculationDetail> recalculationDetails;
private final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor;
private final LocalDate scheduleTillDate;
private final boolean partialUpdate;
private int loanTermInDays;
private final MonetaryCurrency currency;
private final boolean applyInterestRecalculation;
private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate,
final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected,
Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap,
final Map<LocalDate, Money> latePaymentMap, final TreeMap<LocalDate, Money> compoundingMap,
final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
this.periodNumber = periodNumber;
this.instalmentNumber = instalmentNumber;
this.loanTermInDays = loanTermInDays;
this.periodStartDate = periodStartDate;
this.actualRepaymentDate = actualRepaymentDate;
this.totalCumulativePrincipal = totalCumulativePrincipal;
this.totalCumulativeInterest = totalCumulativeInterest;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
this.reducePrincipal = reducePrincipal;
this.principalPortionMap = principalPortionMap;
this.latePaymentMap = latePaymentMap;
this.compoundingMap = compoundingMap;
this.disburseDetailMap = disburseDetailMap;
this.principalToBeScheduled = principalToBeScheduled;
this.outstandingBalance = outstandingBalance;
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
this.installments = installments;
this.recalculationDetails = recalculationDetails;
this.loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessor;
this.scheduleTillDate = scheduleTillDate;
this.partialUpdate = partialUpdate;
this.currency = currency;
this.applyInterestRecalculation = applyInterestRecalculation;
}
public static LoanScheduleParams createLoanScheduleParamsForPartialUpdate(final int periodNumber, final int instalmentNumber,
int loanTermInDays, LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal,
final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal,
final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
final TreeMap<LocalDate, Money> compoundingMap, final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled,
final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
final boolean partialUpdate = true;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency,
applyInterestRecalculation);
}
public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean applyInterestRecalculation) {
final int periodNumber = 1;
final int instalmentNumber = 1;
final LocalDate periodStartDate = null;
final LocalDate actualRepaymentDate = null;
final Money totalCumulativePrincipal = null;
final Money totalCumulativeInterest = null;
final Money totalFeeChargesCharged = null;
final Money totalPenaltyChargesCharged = null;
final Money totalRepaymentExpected = null;
final Money reducePrincipal = null;
final Map<LocalDate, Money> principalPortionMap = null;
final Map<LocalDate, Money> latePaymentMap = null;
final TreeMap<LocalDate, Money> compoundingMap = null;
final Map<LocalDate, Money> disburseDetailMap = null;
final Money principalToBeScheduled = null;
final Money outstandingBalance = null;
final Money outstandingBalanceAsPerRest = null;
final List<LoanRepaymentScheduleInstallment> installments = null;
final boolean partialUpdate = false;
final int loanTermInDays = 0;
final Money totalOutstandingInterestPaymentDueToGrace = null;
final MonetaryCurrency currency = null;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency,
applyInterestRecalculation);
}
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
final LocalDate periodStartDate, final Money principalToBeScheduled) {
final int loanTermInDays = 0;
final int periodNumber = 1;
final int instalmentNumber = 1;
final Money totalCumulativePrincipal = Money.zero(currency);
final Money totalCumulativeInterest = Money.zero(currency);
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
final LocalDate actualRepaymentDate = periodStartDate;
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
final Money totalPenaltyChargesCharged = Money.zero(currency);
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
final Money reducePrincipal = Money.zero(currency);
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Money> disburseDetailMap = new TreeMap<>();
final Money outstandingBalance = principalToBeScheduled;
final Money outstandingBalanceAsPerRest = principalToBeScheduled;
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
final boolean partialUpdate = false;
final Collection<RecalculationDetail> recalculationDetails = null;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
final LocalDate scheduleTillDate = null;
final boolean applyInterestRecalculation = false;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency,
applyInterestRecalculation);
}
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams) {
final int loanTermInDays = 0;
final int periodNumber = 1;
final int instalmentNumber = 1;
final Money totalCumulativePrincipal = Money.zero(currency);
final Money totalCumulativeInterest = Money.zero(currency);
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
final LocalDate actualRepaymentDate = periodStartDate;
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
final Money totalPenaltyChargesCharged = Money.zero(currency);
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
final Money reducePrincipal = Money.zero(currency);
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Money> disburseDetailMap = new TreeMap<>();
final Money outstandingBalance = principalToBeScheduled;
final Money outstandingBalanceAsPerRest = principalToBeScheduled;
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
final boolean partialUpdate = false;
final Collection<RecalculationDetail> recalculationDetails = loanScheduleParams.recalculationDetails;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor;
final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate;
final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments,
recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate, currency,
applyInterestRecalculation);
}
public int getPeriodNumber() {
return this.periodNumber;
}
public int getInstalmentNumber() {
return this.instalmentNumber;
}
public LocalDate getActualRepaymentDate() {
return this.actualRepaymentDate;
}
public Money getTotalCumulativePrincipal() {
return this.totalCumulativePrincipal;
}
public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) {
this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal);
}
public Money getTotalCumulativeInterest() {
return this.totalCumulativeInterest;
}
public void addTotalCumulativeInterest(final Money totalCumulativeInterest) {
this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest);
}
public Money getTotalFeeChargesCharged() {
return this.totalFeeChargesCharged;
}
public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) {
this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged);
}
public Money getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) {
this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged);
}
public Money getTotalRepaymentExpected() {
return this.totalRepaymentExpected;
}
public void addTotalRepaymentExpected(final Money totalRepaymentExpected) {
this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected);
}
public Money getReducePrincipal() {
return this.reducePrincipal;
}
public Map<LocalDate, Money> getPrincipalPortionMap() {
return this.principalPortionMap;
}
public Map<LocalDate, Money> getLatePaymentMap() {
return this.latePaymentMap;
}
public TreeMap<LocalDate, Money> getCompoundingMap() {
return this.compoundingMap;
}
public Map<LocalDate, Money> getDisburseDetailMap() {
return this.disburseDetailMap;
}
public Money getOutstandingBalance() {
return this.outstandingBalance;
}
public Money getOutstandingBalanceAsPerRest() {
return this.outstandingBalanceAsPerRest;
}
public List<LoanRepaymentScheduleInstallment> getInstallments() {
return this.installments;
}
public Collection<RecalculationDetail> getRecalculationDetails() {
return this.recalculationDetails;
}
public LoanRepaymentScheduleTransactionProcessor getLoanRepaymentScheduleTransactionProcessor() {
return this.loanRepaymentScheduleTransactionProcessor;
}
public LocalDate getScheduleTillDate() {
return this.scheduleTillDate;
}
public boolean isPartialUpdate() {
return this.partialUpdate;
}
public LocalDate getPeriodStartDate() {
return this.periodStartDate;
}
public Money getPrincipalToBeScheduled() {
return this.principalToBeScheduled;
}
public void setPeriodNumber(int periodNumber) {
this.periodNumber = periodNumber;
}
public void incrementPeriodNumber() {
this.periodNumber++;
}
public void incrementInstalmentNumber() {
this.instalmentNumber++;
}
public void setPeriodStartDate(LocalDate periodStartDate) {
this.periodStartDate = periodStartDate;
}
public void setActualRepaymentDate(LocalDate actualRepaymentDate) {
this.actualRepaymentDate = actualRepaymentDate;
}
public void setReducePrincipal(Money reducePrincipal) {
this.reducePrincipal = reducePrincipal;
}
public void addReducePrincipal(Money reducePrincipal) {
this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal);
}
public void reduceReducePrincipal(Money reducePrincipal) {
this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal);
}
public void setPrincipalToBeScheduled(Money principalToBeScheduled) {
this.principalToBeScheduled = principalToBeScheduled;
}
public void addPrincipalToBeScheduled(Money principalToBeScheduled) {
this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled);
}
public void setOutstandingBalance(Money outstandingBalance) {
this.outstandingBalance = outstandingBalance;
}
public void addOutstandingBalance(Money outstandingBalance) {
this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance);
}
public void reduceOutstandingBalance(Money outstandingBalance) {
this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance);
}
public void setOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
}
public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest);
}
public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest);
}
public int getLoanTermInDays() {
return this.loanTermInDays;
}
public void addLoanTermInDays(int loanTermInDays) {
this.loanTermInDays += loanTermInDays;
}
public Money getTotalOutstandingInterestPaymentDueToGrace() {
return this.totalOutstandingInterestPaymentDueToGrace;
}
public void setTotalOutstandingInterestPaymentDueToGrace(Money totalOutstandingInterestPaymentDueToGrace) {
this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
}
public Map<LocalDate, TreeMap<LocalDate, Money>> getCompoundingDateVariations() {
return this.compoundingDateVariations;
}
public MonetaryCurrency getCurrency() {
return this.currency;
}
public boolean applyInterestRecalculation() {
return this.applyInterestRecalculation;
}
}
@@ -1,212 +0,0 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.loanschedule.data;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.RecalculationDetail;
public class LoanScheduleRecalculationDTO {
private final int periodNumber;
private final int instalmentNumber;
private final LocalDate periodStartDate;
private final LocalDate actualRepaymentDate;
private final Money totalCumulativePrincipal;
private final Money totalCumulativeInterest;
private final Money totalFeeChargesCharged;
private final Money totalPenaltyChargesCharged;
private final Money totalRepaymentExpected;
private final Money reducePrincipal;
private final Map<LocalDate, Money> principalPortionMap;
private final Map<LocalDate, Money> latePaymentMap;
private final TreeMap<LocalDate, Money> compoundingMap;
private final Map<LocalDate, Money> disburseDetailMap;
private final Money principalToBeScheduled;
private final Money outstandingBalance;
private final Money outstandingBalanceAsPerRest;
private final List<LoanRepaymentScheduleInstallment> installments;
private final Collection<RecalculationDetail> recalculationDetails;
private final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor;
private final LocalDate scheduleTillDate;
private final boolean partialUpdate;
private LoanScheduleRecalculationDTO(final int periodNumber, final int instalmentNumber, LocalDate periodStartDate,
final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected,
final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
final TreeMap<LocalDate, Money> compoundingMap, final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled,
final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean partialUpdate) {
this.periodNumber = periodNumber;
this.instalmentNumber = instalmentNumber;
this.periodStartDate = periodStartDate;
this.actualRepaymentDate = actualRepaymentDate;
this.totalCumulativePrincipal = totalCumulativePrincipal;
this.totalCumulativeInterest = totalCumulativeInterest;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalRepaymentExpected = totalRepaymentExpected;
this.reducePrincipal = reducePrincipal;
this.principalPortionMap = principalPortionMap;
this.latePaymentMap = latePaymentMap;
this.compoundingMap = compoundingMap;
this.disburseDetailMap = disburseDetailMap;
this.principalToBeScheduled = principalToBeScheduled;
this.outstandingBalance = outstandingBalance;
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
this.installments = installments;
this.recalculationDetails = recalculationDetails;
this.loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessor;
this.scheduleTillDate = scheduleTillDate;
this.partialUpdate = partialUpdate;
}
public static LoanScheduleRecalculationDTO createLoanScheduleDTOForPartialUpdate(final int periodNumber, final int instalmentNumber,
LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal,
final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
final Money totalRepaymentExpected, final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap,
final Map<LocalDate, Money> latePaymentMap, final TreeMap<LocalDate, Money> compoundingMap,
final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) {
final boolean partialUpdate = true;
return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap,
principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate);
}
public static LoanScheduleRecalculationDTO createLoanScheduleDTOForCompleteUpdate(
final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) {
final int periodNumber = 1;
final int instalmentNumber = 1;
final LocalDate periodStartDate = null;
final LocalDate actualRepaymentDate = null;
final Money totalCumulativePrincipal = null;
final Money totalCumulativeInterest = null;
final Money totalFeeChargesCharged = null;
final Money totalPenaltyChargesCharged = null;
final Money totalRepaymentExpected = null;
final Money reducePrincipal = null;
final Map<LocalDate, Money> principalPortionMap = null;
final Map<LocalDate, Money> latePaymentMap = null;
final TreeMap<LocalDate, Money> compoundingMap = null;
final Map<LocalDate, Money> disburseDetailMap = null;
final Money principalToBeScheduled = null;
final Money outstandingBalance = null;
final Money outstandingBalanceAsPerRest = null;
final List<LoanRepaymentScheduleInstallment> installments = null;
final boolean partialUpdate = false;
return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap,
principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate);
}
public int getPeriodNumber() {
return this.periodNumber;
}
public int getInstalmentNumber() {
return this.instalmentNumber;
}
public LocalDate getActualRepaymentDate() {
return this.actualRepaymentDate;
}
public Money getTotalCumulativePrincipal() {
return this.totalCumulativePrincipal;
}
public Money getTotalCumulativeInterest() {
return this.totalCumulativeInterest;
}
public Money getTotalFeeChargesCharged() {
return this.totalFeeChargesCharged;
}
public Money getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
public Money getTotalRepaymentExpected() {
return this.totalRepaymentExpected;
}
public Money getReducePrincipal() {
return this.reducePrincipal;
}
public Map<LocalDate, Money> getPrincipalPortionMap() {
return this.principalPortionMap;
}
public Map<LocalDate, Money> getLatePaymentMap() {
return this.latePaymentMap;
}
public TreeMap<LocalDate, Money> getCompoundingMap() {
return this.compoundingMap;
}
public Map<LocalDate, Money> getDisburseDetailMap() {
return this.disburseDetailMap;
}
public Money getOutstandingBalance() {
return this.outstandingBalance;
}
public Money getOutstandingBalanceAsPerRest() {
return this.outstandingBalanceAsPerRest;
}
public List<LoanRepaymentScheduleInstallment> getInstallments() {
return this.installments;
}
public Collection<RecalculationDetail> getRecalculationDetails() {
return this.recalculationDetails;
}
public LoanRepaymentScheduleTransactionProcessor getLoanRepaymentScheduleTransactionProcessor() {
return this.loanRepaymentScheduleTransactionProcessor;
}
public LocalDate getScheduleTillDate() {
return this.scheduleTillDate;
}
public boolean isPartialUpdate() {
return this.partialUpdate;
}
public LocalDate getPeriodStartDate() {
return this.periodStartDate;
}
public Money getPrincipalToBeScheduled() {
return this.principalToBeScheduled;
}
}
@@ -53,8 +53,7 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
@SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
final TreeMap<LocalDate, Money> principalVariation, final Map<LocalDate, Money> compoundingMap,
final LocalDate periodStartDate, final LocalDate periodEndDate, final int daysForInterestInFullPeriod,
final Collection<LoanTermVariationsData> termVariations) {
final LocalDate periodStartDate, final LocalDate periodEndDate, final Collection<LoanTermVariationsData> termVariations) {
LocalDate interestStartDate = periodStartDate;
Money interestForThisInstallment = totalCumulativePrincipal.zero();
@@ -62,16 +61,15 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
Money compoundedInterest = totalCumulativePrincipal.zero();
Money balanceForInterestCalculation = outstandingBalance;
Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace;
final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays();
Map<LocalDate, BigDecimal> interestRates = new HashMap<>(termVariations.size());
for (LoanTermVariationsData loanTermVariation : termVariations) {
if (loanTermVariation.getTermVariationType().isInterestRateVariation()
&& loanTermVariation.isApplicable(periodStartDate, periodEndDate, periodNumber)) {
&& loanTermVariation.isApplicable(periodStartDate, periodEndDate)) {
LocalDate fromDate = loanTermVariation.getTermApplicableFrom();
if (fromDate == null) {
fromDate = periodStartDate;
}
interestRates.put(fromDate, loanTermVariation.getTermValue());
interestRates.put(fromDate, loanTermVariation.getDecimalValue());
if (!principalVariation.containsKey(fromDate)) {
principalVariation.put(fromDate, balanceForInterestCalculation.zero());
}
@@ -93,18 +91,9 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
if (interestForDays > 0) {
final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace,
interestForDays, balanceForInterestCalculation);
if (loanApplicationTerms.getInterestCalculationPeriodMethod().isDaily()) {
interestForThisInstallment = interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
} else {
interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays(
daysForInterestInFullPeriod, result.interest().getAmount(), interestForDays));
cumulatingInterestDueToGrace = cumulatingInterestDueToGrace.plus(calculateInterestForDays(
daysForInterestInFullPeriod, result.interestPaymentDueToGrace().minus(cumulatingInterestDueToGrace)
.getAmount(), interestForDays));
}
balanceForInterestCalculation, interestStartDate, principal.getKey());
interestForThisInstallment = interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
interestStartDate = principal.getKey();
}
Money compoundFee = totalCumulativePrincipal.zero();
@@ -136,20 +125,12 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
clearMapDetails(periodEndDate, compoundingMap);
}
}
int interestForDays = Days.daysBetween(interestStartDate, periodEndDate).getDays();
final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, interestForDays,
balanceForInterestCalculation);
if (loanApplicationTerms.getInterestCalculationPeriodMethod().isDaily()) {
interestForThisInstallment = interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
} else {
interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays(daysForInterestInFullPeriod, result
.interest().getAmount(), interestForDays));
cumulatingInterestDueToGrace = cumulatingInterestDueToGrace.plus(calculateInterestForDays(daysForInterestInFullPeriod, result
.interestPaymentDueToGrace().minus(cumulatingInterestDueToGrace).getAmount(), interestForDays));
}
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace,
balanceForInterestCalculation, interestStartDate, periodEndDate);
interestForThisInstallment = interestForThisInstallment.plus(result.interest());
cumulatingInterestDueToGrace = result.interestPaymentDueToGrace();
Money interestForPeriod = interestForThisInstallment;
if (interestForPeriod.isGreaterThanZero()) {
@@ -157,8 +138,8 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
} else {
interestForPeriod = cumulatingInterestDueToGrace.minus(cumulatingInterestPaymentDueToGrace);
}
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator,
daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc, interestForPeriod);
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance,
periodNumber, mc, interestForPeriod);
// update cumulative fields for principal & interest
final Money interestBroughtFowardDueToGrace = cumulatingInterestDueToGrace;
@@ -29,4 +29,5 @@ public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGenerato
return loanScheduleGenerator;
}
}
@@ -14,15 +14,11 @@ import org.mifosplatform.organisation.holiday.service.HolidayUtil;
import org.mifosplatform.organisation.workingdays.domain.RepaymentRescheduleType;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarHistory;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.common.domain.DayOfWeekType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import java.util.Set;
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override
@@ -52,6 +48,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
dueRepaymentPeriodDate = CalendarUtils.adjustDate(dueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType());
if (currentCalendar != null) {
// If we have currentCalendar object, this means there is a
// calendar associated with
@@ -78,8 +76,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
loanApplicationTerms.getRepaymentEvery(), adjustedDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType
.fromInt(holidayDetailDTO.getWorkingDays().getRepaymentReschedulingType());
final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays()
.getRepaymentReschedulingType());
/**
* Fix for https://mifosforge.jira.com/browse/MIFOSX-1357
@@ -91,6 +89,8 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), nextDueRepaymentPeriodDate, loanApplicationTerms.getNthDay(),
loanApplicationTerms.getWeekDayType());
nextDueRepaymentPeriodDate = CalendarUtils.adjustDate(nextDueRepaymentPeriodDate, loanApplicationTerms.getSeedDate(),
loanApplicationTerms.getRepaymentPeriodFrequencyType());
}
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
@@ -10,7 +10,6 @@ import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
@@ -24,14 +23,13 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
@SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation,
@SuppressWarnings("unused") Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
@SuppressWarnings("unused") int daysForInterestInFullPeriod, @SuppressWarnings("unused") Collection<LoanTermVariationsData> termVariations) {
final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays();
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator,
daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc, null);
@SuppressWarnings("unused") Collection<LoanTermVariationsData> termVariations) {
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance,
periodNumber, mc, null);
final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestPaymentDueToGrace,
daysInPeriodApplicableForInterest, outstandingBalance);
outstandingBalance, periodStartDate, periodEndDate);
Money interestForThisInstallment = result.interest();
// update cumulative fields for principal & interest
@@ -7,14 +7,16 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.Months;
import org.joda.time.Period;
import org.joda.time.PeriodType;
import org.joda.time.Weeks;
import org.joda.time.Years;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
@@ -28,6 +30,7 @@ import org.mifosplatform.portfolio.common.domain.NthDayType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsDataWrapper;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
@@ -46,6 +49,7 @@ public final class LoanApplicationTerms {
private Integer loanTermFrequency;
private final PeriodFrequencyType loanTermPeriodFrequencyType;
private Integer numberOfRepayments;
private Integer actualNumberOfRepayments;
private final Integer repaymentEvery;
private final PeriodFrequencyType repaymentPeriodFrequencyType;
private final Integer nthDay;
@@ -119,12 +123,14 @@ public final class LoanApplicationTerms {
private BigDecimal fixedPrincipalAmount;
private BigDecimal currentPeriodFixedEmiAmount;
private BigDecimal currentPeriodFixedPrincipalAmount;
private final BigDecimal actualFixedEmiAmount;
private final BigDecimal maxOutstandingBalance;
private final List<LoanTermVariationsData> emiAmountVariations;
private Money totalInterestDue;
private final DaysInMonthType daysInMonthType;
@@ -152,7 +158,11 @@ public final class LoanApplicationTerms {
private Money approvedPrincipal = null;
private final Collection<LoanTermVariationsData> loanTermVariations;
private final LoanTermVariationsDataWrapper variationsDataWrapper;
private Money adjustPrincipalForFlatLoans;
private final LocalDate seedDate;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
@@ -164,14 +174,13 @@ public final class LoanApplicationTerms {
final LocalDate calculatedRepaymentsStartingFromDate, final Integer graceOnPrincipalPayment,
final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate,
final Money inArrearsTolerance, final boolean multiDisburseLoan, final BigDecimal emiAmount,
final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
final List<LoanTermVariationsData> emiAmountVariations, final Integer graceOnArrearsAgeing,
final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance, final Integer graceOnArrearsAgeing,
final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled,
final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance,
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
BigDecimal approvedAmount, Collection<LoanTermVariationsData> loanTermVariations) {
BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
@@ -180,7 +189,7 @@ public final class LoanApplicationTerms {
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance,
recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
@@ -192,23 +201,21 @@ public final class LoanApplicationTerms {
final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance,
final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount,
final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
final List<LoanTermVariationsData> emiAmountVariations, final LocalDate interestChargedFromDate,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance,
final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType,
final LocalDate interestChargedFromDate, final BigDecimal principalThresholdForLastInstalment,
final Integer installmentAmountInMultiplesOf, final RecalculationFrequencyType recalculationFrequencyType,
final CalendarInstance restCalendarInstance, final InterestRecalculationCompoundingMethod compoundingMethod,
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate,
Collection<LoanTermVariationsData> loanTermVariations) {
List<LoanTermVariationsData> loanTermVariations) {
final Calendar loanCalendar = null;
return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsTolerance,
loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations,
interestChargedFromDate, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType,
restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar, approvedAmount,
annualNominalInterestRate, loanTermVariations);
loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, interestChargedFromDate,
principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance,
compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, loanPreClosureInterestCalculationStrategy,
rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -217,14 +224,13 @@ public final class LoanApplicationTerms {
final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance,
final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount,
final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
final List<LoanTermVariationsData> emiAmountVariations, final LocalDate interestChargedFromDate,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance,
final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType,
final LocalDate interestChargedFromDate, final BigDecimal principalThresholdForLastInstalment,
final Integer installmentAmountInMultiplesOf, final RecalculationFrequencyType recalculationFrequencyType,
final CalendarInstance restCalendarInstance, final InterestRecalculationCompoundingMethod compoundingMethod,
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount,
BigDecimal annualNominalInterestRate, Collection<LoanTermVariationsData> loanTermVariations) {
BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -251,11 +257,11 @@ public final class LoanApplicationTerms {
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance,
multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations,
loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, loanProductRelatedDetail.getGraceOnDueDate(),
daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, compoundingMethod,
restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType,
principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy,
loanCalendar, approvedAmount, loanTermVariations);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -263,13 +269,12 @@ public final class LoanApplicationTerms {
final LocalDate repaymentsStartingFromDate, final LocalDate calculatedRepaymentsStartingFromDate,
final Money inArrearsTolerance, final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan,
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 CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment,
final Integer installmentAmountInMultiplesOf,
final LocalDate interestChargedFromDate, final LoanInterestRecalculationDetails interestRecalculationDetails,
final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType,
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, BigDecimal approvedAmount,
final BigDecimal annualNominalInterestRate, final Collection<LoanTermVariationsData> loanTermVariations) {
final BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -303,11 +308,31 @@ public final class LoanApplicationTerms {
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(),
daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod,
interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
disbursementDatas, maxOutstandingBalance, loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance,
recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf, loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
}
public static LoanApplicationTerms assembleFrom(final LoanApplicationTerms applicationTerms,
final List<LoanTermVariationsData> loanTermVariations) {
return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency,
applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, applicationTerms.repaymentEvery,
applicationTerms.repaymentPeriodFrequencyType, applicationTerms.nthDay, applicationTerms.weekDayType,
applicationTerms.amortizationMethod, applicationTerms.interestMethod, applicationTerms.interestRatePerPeriod,
applicationTerms.interestRatePeriodFrequencyType, applicationTerms.annualNominalInterestRate,
applicationTerms.interestCalculationPeriodMethod, applicationTerms.principal, applicationTerms.expectedDisbursementDate,
applicationTerms.repaymentsStartingFromDate, applicationTerms.calculatedRepaymentsStartingFromDate,
applicationTerms.principalGrace, applicationTerms.interestPaymentGrace, applicationTerms.interestChargingGrace,
applicationTerms.interestChargedFromDate, applicationTerms.inArrearsTolerance, applicationTerms.multiDisburseLoan,
applicationTerms.actualFixedEmiAmount, applicationTerms.disbursementDatas, applicationTerms.maxOutstandingBalance,
applicationTerms.graceOnArrearsAgeing, applicationTerms.daysInMonthType, applicationTerms.daysInYearType,
applicationTerms.interestRecalculationEnabled, applicationTerms.rescheduleStrategyMethod,
applicationTerms.interestRecalculationCompoundingMethod, applicationTerms.restCalendarInstance,
applicationTerms.recalculationFrequencyType, applicationTerms.compoundingCalendarInstance,
applicationTerms.compoundingFrequencyType, applicationTerms.principalThresholdForLastInstalment,
applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy,
applicationTerms.loanCalendar, applicationTerms.approvedPrincipal.getAmount(), loanTermVariations);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -320,15 +345,15 @@ public final class LoanApplicationTerms {
final LocalDate calculatedRepaymentsStartingFromDate, final Integer principalGrace, final Integer interestPaymentGrace,
final Integer interestChargingGrace, final LocalDate interestChargedFromDate, final Money inArrearsTolerance,
final boolean multiDisburseLoan, final BigDecimal emiAmount, final List<DisbursementData> disbursementDatas,
final BigDecimal maxOutstandingBalance, final List<LoanTermVariationsData> emiAmountVariations,
final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
final boolean isInterestRecalculationEnabled, final LoanRescheduleStrategyMethod rescheduleStrategyMethod,
final BigDecimal maxOutstandingBalance, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType,
final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled,
final LoanRescheduleStrategyMethod rescheduleStrategyMethod,
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod,
final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType,
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
BigDecimal approvedAmount, Collection<LoanTermVariationsData> loanTermVariations) {
BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
@@ -361,7 +386,6 @@ public final class LoanApplicationTerms {
this.actualFixedEmiAmount = emiAmount;
this.disbursementDatas = disbursementDatas;
this.maxOutstandingBalance = maxOutstandingBalance;
this.emiAmountVariations = emiAmountVariations;
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
this.daysInMonthType = daysInMonthType;
this.daysInYearType = daysInYearType;
@@ -378,7 +402,14 @@ public final class LoanApplicationTerms {
this.loanCalendar = loanCalendar;
this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount);
this.loanTermVariations = loanTermVariations;
this.variationsDataWrapper = new LoanTermVariationsDataWrapper(loanTermVariations);
this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments();
this.adjustPrincipalForFlatLoans = principal.zero();
if (this.calculatedRepaymentsStartingFromDate == null) {
this.seedDate = this.expectedDisbursementDate;
} else {
this.seedDate = this.calculatedRepaymentsStartingFromDate;
}
}
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -398,7 +429,7 @@ public final class LoanApplicationTerms {
if (difference.isLessThan(principalThreshold)) {
adjusted = principalForPeriod.plus(difference.abs());
}
} else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) {
} else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, periodNumber)) {
final Money difference = totalCumulativePrincipalToDate.minus(this.principal);
if (difference.isLessThanZero()) {
@@ -421,7 +452,7 @@ public final class LoanApplicationTerms {
// paid too much interest, subtract amount that overpays from
// interest paid for period.
adjusted = interestForThisPeriod.minus(totalInterestRemaining.abs());
} else if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) {
} else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, periodNumber)) {
final Money interestDifference = totalCumulativeInterestToDate.minus(totalInterestDueForLoan);
if (interestDifference.isLessThanZero()) {
adjusted = interestForThisPeriod.plus(interestDifference.abs());
@@ -463,22 +494,19 @@ public final class LoanApplicationTerms {
return totalInterestCharged;
}
public Money calculateTotalPrincipalForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final int daysInPeriod,
final Money outstandingBalance, final int periodNumber, final MathContext mc, Money interestForThisInstallment) {
public Money calculateTotalPrincipalForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final Money outstandingBalance,
final int periodNumber, final MathContext mc, Money interestForThisInstallment) {
Money principalForInstallment = this.principal.zero();
switch (this.interestMethod) {
case FLAT:
principalForInstallment = calculateTotalPrincipalPerPeriodWithoutGrace(mc);
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
principalForInstallment = Money.zero(principalForInstallment.getCurrency());
}
principalForInstallment = calculateTotalPrincipalPerPeriodWithoutGrace(mc, periodNumber);
break;
case DECLINING_BALANCE:
switch (this.amortizationMethod) {
case EQUAL_INSTALLMENTS:
Money totalPmtForThisInstallment = pmtForInstallment(calculator, daysInPeriod, outstandingBalance, periodNumber, mc);
Money totalPmtForThisInstallment = pmtForInstallment(calculator, outstandingBalance, periodNumber, mc);
principalForInstallment = calculatePrincipalDueForInstallment(periodNumber, totalPmtForThisInstallment,
interestForThisInstallment);
break;
@@ -496,22 +524,47 @@ public final class LoanApplicationTerms {
return principalForInstallment;
}
public Money pmtForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final int daysInPeriod,
final Money outstandingBalance, final int periodNumber, final MathContext mc) {
public Money pmtForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final Money outstandingBalance,
final int periodNumber, final MathContext mc) {
// Calculate exact period from disbursement date
final LocalDate periodStartDate = getExpectedDisbursementDate().withDayOfMonth(1);
final LocalDate periodEndDate = getPeriodEndDate(periodStartDate);
// equal installments
final int periodsElapsed = periodNumber - 1;
// with periodic interest for default month and year for
// equal installment
final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc, daysInPeriod,
DaysInMonthType.DAYS_30, DaysInYearType.DAYS_365);
final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc, DaysInMonthType.DAYS_30,
DaysInYearType.DAYS_365, periodStartDate, periodEndDate);
Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(periodicInterestRateForRepaymentPeriod,
outstandingBalance, periodsElapsed);
return totalPmtForThisInstallment;
}
private LocalDate getPeriodEndDate(final LocalDate startDate) {
LocalDate dueRepaymentPeriodDate = startDate;
switch (this.repaymentPeriodFrequencyType) {
case DAYS:
dueRepaymentPeriodDate = startDate.plusDays(this.repaymentEvery);
break;
case WEEKS:
dueRepaymentPeriodDate = startDate.plusWeeks(this.repaymentEvery);
break;
case MONTHS:
dueRepaymentPeriodDate = startDate.plusMonths(this.repaymentEvery);
break;
case YEARS:
dueRepaymentPeriodDate = startDate.plusYears(this.repaymentEvery);
break;
case INVALID:
break;
}
return dueRepaymentPeriodDate;
}
public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc,
final Money cumulatingInterestPaymentDueToGrace, final int daysInPeriodApplicableForInterest, final Money outstandingBalance) {
final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LocalDate periodStartDate,
final LocalDate periodEndDate) {
Money interestForInstallment = this.principal.zero();
Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy();
@@ -541,11 +594,11 @@ public final class LoanApplicationTerms {
case DECLINING_BALANCE:
final Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(
calculator, mc, daysInPeriodApplicableForInterest, outstandingBalance);
calculator, mc, outstandingBalance, periodStartDate, periodEndDate);
final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace(
calculator, interestCalculationGraceOnRepaymentPeriodFraction, mc, daysInPeriodApplicableForInterest,
outstandingBalance, periodNumber);
calculator, interestCalculationGraceOnRepaymentPeriodFraction, mc, outstandingBalance, periodNumber,
periodStartDate, periodEndDate);
interestForInstallment = interestForThisInstallmentAfterGrace;
if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) {
@@ -628,12 +681,45 @@ public final class LoanApplicationTerms {
case INVALID:
break;
case SAME_AS_REPAYMENT_PERIOD:
LocalDate startDate = getExpectedDisbursementDate();
periodsInLoanTerm = calculatePeriodsBetweenDates(startDate, this.loanEndDate);
break;
}
return periodsInLoanTerm;
}
public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final LocalDate endDate) {
BigDecimal numberOfPeriods = BigDecimal.ZERO;
switch (this.repaymentPeriodFrequencyType) {
case DAYS:
int numberOfDays = Days.daysBetween(startDate, endDate).getDays();
numberOfPeriods = BigDecimal.valueOf((double) numberOfDays);
break;
case WEEKS:
int numberOfWeeks = Weeks.weeksBetween(startDate, endDate).getWeeks();
int daysLeftAfterWeeks = Days.daysBetween(startDate.plusWeeks(numberOfWeeks), endDate).getDays();
numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfWeeks)).add(
BigDecimal.valueOf((double) daysLeftAfterWeeks / 7));
break;
case MONTHS:
int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths();
int daysLeftAfterMonths = Days.daysBetween(startDate.plusMonths(numberOfMonths), endDate).getDays();
numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfMonths)).add(
BigDecimal.valueOf((double) daysLeftAfterMonths / 30));
break;
case YEARS:
int numberOfYears = Years.yearsBetween(startDate, endDate).getYears();
int daysLeftAfterYears = Days.daysBetween(startDate.plusYears(numberOfYears), endDate).getDays();
numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfYears)).add(
BigDecimal.valueOf((double) daysLeftAfterYears / 365));
break;
default:
break;
}
return numberOfPeriods;
}
public void updateLoanEndDate(final LocalDate loanEndDate) {
this.loanEndDate = loanEndDate;
}
@@ -642,12 +728,23 @@ public final class LoanApplicationTerms {
final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc);
return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.numberOfRepayments), mc.getRoundingMode());
return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments), mc.getRoundingMode());
}
private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc) {
private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc, final int periodNumber) {
final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment();
return this.principal.dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode());
Money principalPerPeriod = this.principal.dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus(
this.adjustPrincipalForFlatLoans);
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
principalPerPeriod = principalPerPeriod.zero();
}
if (!isPrincipalGraceApplicableForThisPeriod(periodNumber) && currentPeriodFixedPrincipalAmount != null) {
this.adjustPrincipalForFlatLoans = this.adjustPrincipalForFlatLoans.plus(principalPerPeriod.minus(
currentPeriodFixedPrincipalAmount).dividedBy(this.actualNumberOfRepayments - periodNumber, mc.getRoundingMode()));
principalPerPeriod = this.principal.zero().plus(currentPeriodFixedPrincipalAmount);
}
return principalPerPeriod;
}
private PrincipalInterest calculateTotalFlatInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
@@ -693,7 +790,7 @@ public final class LoanApplicationTerms {
final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace());
final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree);
final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.numberOfRepayments);
final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments);
interestForInstallment = realTotalInterestForLoan
.dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode());
@@ -702,8 +799,8 @@ public final class LoanApplicationTerms {
return interestForInstallment;
}
private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, final int actualDays,
final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType) {
private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc,
final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, LocalDate periodStartDate, LocalDate periodEndDate) {
final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
@@ -711,13 +808,13 @@ public final class LoanApplicationTerms {
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
BigDecimal periodicInterestRate = BigDecimal.ZERO;
BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsBetweenDates(periodStartDate, periodEndDate);
switch (this.interestCalculationPeriodMethod) {
case INVALID:
break;
case DAILY:
// For daily work out number of days in the period
int numberOfDaysInPeriod = actualDays;
BigDecimal numberOfDaysInPeriod = BigDecimal.valueOf(Days.daysBetween(periodStartDate, periodEndDate).getDays());
final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc)
.divide(divisor, mc);
@@ -726,38 +823,36 @@ public final class LoanApplicationTerms {
case INVALID:
break;
case DAYS:
final BigDecimal loanTermFrequencyBigDecimal = BigDecimal.valueOf(numberOfDaysInPeriod);
periodicInterestRate = oneDayOfYearInterestRate.multiply(loanTermFrequencyBigDecimal, mc);
periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
break;
case WEEKS:
periodicInterestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(numberOfDaysInPeriod), mc);
periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
break;
case MONTHS:
if (daysInMonthType.isDaysInMonth_30()) {
numberOfDaysInPeriod = this.repaymentEvery * 30;
numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(30), mc);
}
periodicInterestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(numberOfDaysInPeriod), mc);
periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
break;
case YEARS:
switch (daysInYearType) {
case DAYS_360:
numberOfDaysInPeriod = this.repaymentEvery * 360;
numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(360), mc);
break;
case DAYS_364:
numberOfDaysInPeriod = this.repaymentEvery * 364;
numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(364), mc);
break;
case DAYS_365:
numberOfDaysInPeriod = this.repaymentEvery * 365;
numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(365), mc);
break;
default:
break;
}
periodicInterestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(numberOfDaysInPeriod), mc);
periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc);
break;
}
break;
case SAME_AS_REPAYMENT_PERIOD:
final BigDecimal loanTermFrequencyBigDecimal = BigDecimal.valueOf(this.repaymentEvery);
periodicInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
.multiply(loanTermFrequencyBigDecimal);
break;
@@ -808,7 +903,7 @@ public final class LoanApplicationTerms {
}
private int calculateNumberOfRepaymentsWithPrincipalPayment() {
return this.numberOfRepayments - getPrincipalGrace();
return this.actualNumberOfRepayments - getPrincipalGrace();
}
private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(final Integer totalNumberOfRepaymentPeriods) {
@@ -861,11 +956,11 @@ public final class LoanApplicationTerms {
private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) {
if (this.fixedEmiAmount == null) {
if (getFixedEmiAmount() == null) {
final double futureValue = 0;
final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
final Integer periodsRemaining = this.numberOfRepayments - periodsElapsed;
final Integer periodsRemaining = this.actualNumberOfRepayments - periodsElapsed;
double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(),
principalDouble, futureValue, false);
@@ -875,26 +970,27 @@ public final class LoanApplicationTerms {
}
setFixedEmiAmount(BigDecimal.valueOf(installmentAmount));
}
return this.fixedEmiAmount.doubleValue();
return getFixedEmiAmount().doubleValue();
}
private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
final MathContext mc, final int daysInPeriod, final Money outstandingBalance) {
final MathContext mc, final Money outstandingBalance, LocalDate periodStartDate, LocalDate periodEndDate) {
Money interestDue = Money.zero(outstandingBalance.getCurrency());
final BigDecimal periodicInterestRate = periodicInterestRate(calculator, mc, daysInPeriod, this.daysInMonthType,
this.daysInYearType);
final BigDecimal periodicInterestRate = periodicInterestRate(calculator, mc, this.daysInMonthType, this.daysInYearType,
periodStartDate, periodEndDate);
interestDue = outstandingBalance.multiplyRetainScale(periodicInterestRate, mc.getRoundingMode());
return interestDue;
}
private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final int daysInPeriod,
final Money outstandingBalance, final int periodNumber) {
final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance,
final int periodNumber, LocalDate periodStartDate, LocalDate periodEndDate) {
Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, daysInPeriod, outstandingBalance);
Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance,
periodStartDate, periodEndDate);
if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) {
interest = interest.zero();
@@ -927,15 +1023,13 @@ public final class LoanApplicationTerms {
}
private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) {
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.numberOfRepayments);
Money principal = this.principal;
if (this.fixedPrincipalAmount == null) {
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.actualNumberOfRepayments);
principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode());
this.fixedPrincipalAmount = principal.getAmount();
} else {
principal = Money.of(getCurrency(), this.fixedPrincipalAmount);
}
principal = Money.of(getCurrency(), getFixedPrincipalAmount());
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
principal = principal.zero();
@@ -944,11 +1038,15 @@ public final class LoanApplicationTerms {
}
public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) {
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.numberOfRepayments);
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.actualNumberOfRepayments);
Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods - periodNumber + 1, mc.getRoundingMode());
this.fixedPrincipalAmount = principal.getAmount();
}
public void setFixedPrincipalAmount(BigDecimal fixedPrincipalAmount) {
this.fixedPrincipalAmount = fixedPrincipalAmount;
}
private Money calculatePrincipalDueForInstallment(final int periodNumber, final Money totalDuePerInstallment, final Money periodInterest) {
Money principal = totalDuePerInstallment.minus(periodInterest);
@@ -1067,7 +1165,11 @@ public final class LoanApplicationTerms {
}
public BigDecimal getFixedEmiAmount() {
return this.fixedEmiAmount;
BigDecimal fixedEmiAmount = this.fixedEmiAmount;
if (getCurrentPeriodFixedEmiAmount() != null) {
fixedEmiAmount = getCurrentPeriodFixedEmiAmount();
}
return fixedEmiAmount;
}
public Integer getNthDay() {
@@ -1078,19 +1180,6 @@ public final class LoanApplicationTerms {
return this.weekDayType;
}
public void setFixedEmiAmountForPeriod(LocalDate periodDate) {
LocalDate startDate = expectedDisbursementDate;
for (LoanTermVariationsData loanVariationTermsData : this.emiAmountVariations) {
if (!periodDate.isBefore(loanVariationTermsData.getTermApplicableFrom())
&& !startDate.isAfter(loanVariationTermsData.getTermApplicableFrom())) {
if (loanVariationTermsData.getTermValue() != null) {
this.fixedEmiAmount = loanVariationTermsData.getTermValue();
startDate = loanVariationTermsData.getTermApplicableFrom();
}
}
}
}
public void setFixedEmiAmount(BigDecimal fixedEmiAmount) {
this.fixedEmiAmount = fixedEmiAmount;
}
@@ -1185,6 +1274,8 @@ public final class LoanApplicationTerms {
public void updateNumberOfRepayments(final Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments();
}
public void updatePrincipalGrace(final Integer principalGrace) {
@@ -1259,11 +1350,39 @@ public final class LoanApplicationTerms {
}
public BigDecimal getFixedPrincipalAmount() {
return this.fixedPrincipalAmount;
BigDecimal fixedPrincipalAmount = this.fixedPrincipalAmount;
if (getCurrentPeriodFixedPrincipalAmount() != null) {
fixedPrincipalAmount = getCurrentPeriodFixedPrincipalAmount();
}
return fixedPrincipalAmount;
}
public Collection<LoanTermVariationsData> getLoanTermVariations() {
return this.loanTermVariations;
public LoanTermVariationsDataWrapper getLoanTermVariations() {
return this.variationsDataWrapper;
}
public BigDecimal getCurrentPeriodFixedEmiAmount() {
return this.currentPeriodFixedEmiAmount;
}
public void setCurrentPeriodFixedEmiAmount(BigDecimal currentPeriodFixedEmiAmount) {
this.currentPeriodFixedEmiAmount = currentPeriodFixedEmiAmount;
}
public BigDecimal getCurrentPeriodFixedPrincipalAmount() {
return this.currentPeriodFixedPrincipalAmount;
}
public void setCurrentPeriodFixedPrincipalAmount(BigDecimal currentPeriodFixedPrincipalAmount) {
this.currentPeriodFixedPrincipalAmount = currentPeriodFixedPrincipalAmount;
}
public Integer fetchNumberOfRepaymentsAfterExceptions() {
return this.actualNumberOfRepayments;
}
public LocalDate getSeedDate() {
return this.seedDate;
}
}
@@ -0,0 +1,16 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.loanschedule.exception;
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class ScheduleDateException extends AbstractPlatformDomainRuleException {
public ScheduleDateException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.loanschedule.schedule.due.date.not.valid", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -10,15 +10,25 @@ import java.math.MathContext;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang.StringUtils;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
import org.mifosplatform.organisation.holiday.domain.HolidayStatusType;
@@ -36,6 +46,7 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
@@ -57,10 +68,14 @@ import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariations;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationsComparator;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
@@ -70,7 +85,9 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicati
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.mifosplatform.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator;
import org.mifosplatform.portfolio.loanaccount.service.LoanChargeAssembler;
import org.mifosplatform.portfolio.loanaccount.service.LoanUtilService;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
@@ -80,9 +97,11 @@ 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.LoanProductVariableInstallmentConfig;
import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -106,6 +125,10 @@ public class LoanScheduleAssembler {
private final GroupRepositoryWrapper groupRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator;
private final CalendarInstanceRepository calendarInstanceRepository;
private final PlatformSecurityContext context;
private final LoanUtilService loanUtilService;
@Autowired
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
@@ -115,7 +138,10 @@ public class LoanScheduleAssembler {
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator,
final CalendarInstanceRepository calendarInstanceRepository, final PlatformSecurityContext context,
final LoanUtilService loanUtilService) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.loanProductRepository = loanProductRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
@@ -129,6 +155,10 @@ public class LoanScheduleAssembler {
this.groupRepository = groupRepository;
this.workingDaysRepository = workingDaysRepository;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.variableLoanScheduleFromApiJsonValidator = variableLoanScheduleFromApiJsonValidator;
this.calendarInstanceRepository = calendarInstanceRepository;
this.context = context;
this.loanUtilService = loanUtilService;
}
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
@@ -256,11 +286,6 @@ public class LoanScheduleAssembler {
final List<DisbursementData> disbursementDatas = fetchDisbursementData(element.getAsJsonObject());
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<>();
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount);
loanVariationTermsData.add(data);
/**
* Interest recalculation settings copy from product definition
*/
@@ -307,28 +332,29 @@ public class LoanScheduleAssembler {
final Integer installmentAmountInMultiplesOf = loanProduct.getInstallmentAmountInMultiplesOf();
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
final BigDecimal interestRateDiff = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
LoanApiConstants.interestRateDifferentialParameterName, element);
final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(
LoanApiConstants.isFloatingInterestRateParameterName, element);
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try{
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
}catch(final FloatingRateNotFoundException ex){
// Do not do anything
try {
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
} catch (final FloatingRateNotFoundException ex) {
// Do not do anything
}
FloatingRateDTO floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, expectedDisbursementDate, interestRateDiff,
baseLendingRatePeriods);
Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(floatingRateDTO);
LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant();
final LocalDate dateValue = null;
final boolean isSpecificToInstallment = false;
for (FloatingRatePeriodData periodData : applicableRates) {
LoanTermVariationsData loanTermVariation = new LoanTermVariationsData(
LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(),
periodData.getInterestRate());
periodData.getInterestRate(), dateValue, isSpecificToInstallment);
if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) {
interestRateStartDate = periodData.getFromDateAsLocalDate();
annualNominalInterestRate = periodData.getInterestRate();
@@ -342,11 +368,10 @@ public class LoanScheduleAssembler {
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney,
loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData,
graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType,
restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO,
loanTermVariations);
loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing,
daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, restCalendarInstance,
compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO, loanTermVariations);
}
private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate,
@@ -534,6 +559,345 @@ public class LoanScheduleAssembler {
}
public void assempleVariableScheduleFrom(final Loan loan, final String json) {
this.variableLoanScheduleFromApiJsonValidator.validateSchedule(json, loan);
List<LoanTermVariations> variations = loan.getLoanTermVariations();
List<LoanTermVariations> newVariations = new ArrayList<>();
extractLoanTermVariations(loan, json, newVariations);
final Map<LocalDate, LocalDate> adjustDueDateVariations = new HashMap<>();
if (!variations.isEmpty()) {
List<LoanTermVariations> retainVariations = adjustExistingVariations(variations, newVariations, adjustDueDateVariations);
newVariations = retainVariations;
}
variations.addAll(newVariations);
Collections.sort(variations, new LoanTermVariationsComparator());
/*
* List<LoanTermVariationsData> loanTermVariationsDatas = new
* ArrayList<>();
* loanTermVariationsDatas.addAll(loanApplicationTerms.getLoanTermVariations
* ().getExceptionData()); loanApplicationTerms =
* LoanApplicationTerms.assembleFrom(loanApplicationTerms,
* loanTermVariationsDatas);
*/
// date validations
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
Set<LocalDate> dueDates = new TreeSet<>();
LocalDate graceApplicable = loan.getExpectedDisbursedOnLocalDate();
Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().graceOnPrincipalPayment();
if (graceOnPrincipal == null) {
graceOnPrincipal = 0;
}
LocalDate lastDate = loan.getExpectedDisbursedOnLocalDate();
for (LoanRepaymentScheduleInstallment installment : installments) {
dueDates.add(installment.getDueDate());
if (lastDate.isBefore(installment.getDueDate())) {
lastDate = installment.getDueDate();
}
if (graceOnPrincipal.equals(installment.getInstallmentNumber())) {
graceApplicable = installment.getDueDate();
}
}
Collection<LocalDate> keySet = adjustDueDateVariations.keySet();
dueDates.addAll(keySet);
for (final LocalDate date : keySet) {
LocalDate removeDate = adjustDueDateVariations.get(date);
if (removeDate != null) {
dueDates.remove(removeDate);
}
}
Set<LocalDate> actualDueDates = new TreeSet<>(dueDates);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
List<LocalDate> overlappings = new ArrayList<>();
for (LoanTermVariations termVariations : variations) {
switch (termVariations.getTermType()) {
case INSERT_INSTALLMENT:
if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
overlappings.add(termVariations.fetchTermApplicaDate());
} else {
dueDates.add(termVariations.fetchTermApplicaDate());
}
if (!graceApplicable.isBefore(termVariations.fetchTermApplicaDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.insert.not.allowed.before.grace.period", "Loan schedule insert request invalid");
}
if (termVariations.fetchTermApplicaDate().isAfter(lastDate)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.insert.not.allowed.after.last.period.date", "Loan schedule insert request invalid");
} else if(termVariations.fetchTermApplicaDate().isBefore(loan.getExpectedDisbursedOnLocalDate())){
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid");
}
break;
case DELETE_INSTALLMENT:
if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
dueDates.remove(termVariations.fetchTermApplicaDate());
} else {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.remove.date.invalid",
"Loan schedule remove request invalid");
}
if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.delete.not.allowed.for.last.period.date", "Loan schedule remove request invalid");
}
break;
case DUE_DATE:
if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
if (overlappings.contains(termVariations.fetchTermApplicaDate())) {
overlappings.remove(termVariations.fetchTermApplicaDate());
} else {
dueDates.remove(termVariations.fetchTermApplicaDate());
}
} else {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.modify.date.invalid",
"Loan schedule modify due date request invalid");
}
if (dueDates.contains(termVariations.fetchDateValue())) {
overlappings.add(termVariations.fetchDateValue());
} else {
dueDates.add(termVariations.fetchDateValue());
}
if(termVariations.fetchDateValue().isBefore(loan.getExpectedDisbursedOnLocalDate())){
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid");
}
if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) {
lastDate = termVariations.fetchDateValue();
}
break;
case PRINCIPAL_AMOUNT:
case EMI_AMOUNT:
if (!graceApplicable.isBefore(termVariations.fetchTermApplicaDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.amount.update.not.allowed.before.grace.period", "Loan schedule modify request invalid");
}
if (!dueDates.contains(termVariations.fetchTermApplicaDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.amount.update.from.date.invalid", "Loan schedule modify request invalid");
}
if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.amount.update.not.allowed.for.last.period", "Loan schedule modify request invalid");
}
break;
default:
break;
}
}
if (!overlappings.isEmpty()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.modify.date.can.not.be.due.date",
overlappings);
}
LoanProductVariableInstallmentConfig installmentConfig = loan.loanProduct().loanProductVariableInstallmentConfig();
final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
Calendar loanCalendar = null;
if (loanCalendarInstance != null) {
loanCalendar = loanCalendarInstance.getCalendar();
}
final Integer minGap = installmentConfig.getMinimumGap();
final Integer maxGap = installmentConfig.getMaximumGap();
LocalDate previousDate = loan.getDisbursementDate();
for (LocalDate duedate : dueDates) {
int gap = Days.daysBetween(previousDate, duedate).getDays();
previousDate = duedate;
if (gap < minGap || (maxGap != null && gap > maxGap)) {
baseDataValidator.reset().value(duedate)
.failWithCodeNoParameterAddedToErrorCode("variable.schedule.date.must.be.in.min.max.range", "Loan schedule date invalid");
} else if (loanCalendar != null && !actualDueDates.contains(duedate) && !loanCalendar.isValidRecurringDate(duedate)) {
baseDataValidator
.reset()
.value(duedate)
.failWithCodeNoParameterAddedToErrorCode("variable.schedule.date.not.meeting.date",
"Loan schedule date not in sync with meeting date");
}
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
if (loan.getExpectedFirstRepaymentOnDate() == null) {
loan.setExpectedFirstRepaymentOnDate(loan.fetchRepaymentScheduleInstallment(1).getDueDate().toDate());
}
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan);
AppUser currentUser = this.context.getAuthenticatedUserIfPresent();
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
}
private List<LoanTermVariations> adjustExistingVariations(List<LoanTermVariations> variations, List<LoanTermVariations> newVariations,
final Map<LocalDate, LocalDate> adjustDueDateVariations) {
Map<LocalDate, LoanTermVariations> amountVariations = new HashMap<>();
Map<LocalDate, LoanTermVariations> dueDateVariations = new HashMap<>();
Map<LocalDate, LoanTermVariations> insertVariations = new HashMap<>();
for (LoanTermVariations loanTermVariations : variations) {
switch (loanTermVariations.getTermType()) {
case EMI_AMOUNT:
case PRINCIPAL_AMOUNT:
amountVariations.put(loanTermVariations.fetchTermApplicaDate(), loanTermVariations);
break;
case DUE_DATE:
dueDateVariations.put(loanTermVariations.fetchDateValue(), loanTermVariations);
adjustDueDateVariations.put(loanTermVariations.fetchTermApplicaDate(), loanTermVariations.fetchDateValue());
break;
case INSERT_INSTALLMENT:
insertVariations.put(loanTermVariations.fetchTermApplicaDate(), loanTermVariations);
adjustDueDateVariations.put(loanTermVariations.fetchTermApplicaDate(), loanTermVariations.fetchTermApplicaDate());
break;
case DELETE_INSTALLMENT:
adjustDueDateVariations.put(loanTermVariations.fetchTermApplicaDate(), null);
break;
default:
break;
}
}
List<LoanTermVariations> retainVariations = new ArrayList<>();
for (LoanTermVariations loanTermVariations : newVariations) {
boolean retain = true;
switch (loanTermVariations.getTermType()) {
case DUE_DATE:
if (amountVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
amountVariations.get(loanTermVariations.fetchTermApplicaDate()).setTermApplicableFrom(
loanTermVariations.getDateValue());
} else if (insertVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
insertVariations.get(loanTermVariations.fetchTermApplicaDate()).setTermApplicableFrom(
loanTermVariations.getDateValue());
retain = false;
}
if (dueDateVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
LoanTermVariations existingVariation = dueDateVariations.get(loanTermVariations.fetchTermApplicaDate());
if (existingVariation.fetchTermApplicaDate().isEqual(loanTermVariations.fetchDateValue())) {
variations.remove(existingVariation);
} else {
existingVariation.setTermApplicableFrom(loanTermVariations.getDateValue());
}
retain = false;
}
break;
case EMI_AMOUNT:
case PRINCIPAL_AMOUNT:
if (amountVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
amountVariations.get(loanTermVariations.fetchTermApplicaDate()).setDecimalValue(loanTermVariations.getTermValue());
retain = false;
} else if (insertVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
insertVariations.get(loanTermVariations.fetchTermApplicaDate()).setDecimalValue(loanTermVariations.getTermValue());
retain = false;
}
break;
case DELETE_INSTALLMENT:
if (amountVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
variations.remove(amountVariations.get(loanTermVariations.fetchTermApplicaDate()));
} else if (insertVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
variations.remove(insertVariations.get(loanTermVariations.fetchTermApplicaDate()));
retain = false;
}
if (dueDateVariations.containsKey(loanTermVariations.fetchTermApplicaDate())) {
variations.remove(amountVariations.get(loanTermVariations.fetchTermApplicaDate()));
}
break;
default:
break;
}
if (retain) {
retainVariations.add(loanTermVariations);
}
}
return retainVariations;
}
private void extractLoanTermVariations(final Loan loan, final String json, final List<LoanTermVariations> loanTermVariations) {
final JsonElement element = this.fromApiJsonHelper.parse(json);
if (loan.loanProduct().allowVariabeInstallments()) {
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.exceptionParamName, element)) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final JsonObject exceptionObject = topLevelJsonElement.getAsJsonObject(LoanApiConstants.exceptionParamName);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.modifiedinstallmentsParamName, exceptionObject)
&& exceptionObject.get(LoanApiConstants.modifiedinstallmentsParamName).isJsonArray()) {
final JsonArray modificationsArray = exceptionObject.get(LoanApiConstants.modifiedinstallmentsParamName)
.getAsJsonArray();
extractLoanTermVariations(loan, dateFormat, locale, modificationsArray, false, false, loanTermVariations);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.newinstallmentsParamName, exceptionObject)
&& exceptionObject.get(LoanApiConstants.newinstallmentsParamName).isJsonArray()) {
final JsonArray array = exceptionObject.get(LoanApiConstants.newinstallmentsParamName).getAsJsonArray();
extractLoanTermVariations(loan, dateFormat, locale, array, true, false, loanTermVariations);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.deletedinstallmentsParamName, exceptionObject)
&& exceptionObject.get(LoanApiConstants.deletedinstallmentsParamName).isJsonArray()) {
final JsonArray array = exceptionObject.get(LoanApiConstants.deletedinstallmentsParamName).getAsJsonArray();
extractLoanTermVariations(loan, dateFormat, locale, array, false, true, loanTermVariations);
}
}
}
}
private void extractLoanTermVariations(final Loan loan, final String dateFormat, final Locale locale,
final JsonArray modificationsArray, final boolean isInsertInstallment, final boolean isDeleteInstallment,
final List<LoanTermVariations> loanTermVariations) {
for (int i = 1; i <= modificationsArray.size(); i++) {
final JsonObject arrayElement = modificationsArray.get(i - 1).getAsJsonObject();
BigDecimal decimalValue = null;
LoanTermVariationType decimalValueVariationType = LoanTermVariationType.INVALID;
if (loan.getLoanProductRelatedDetail().getAmortizationMethod().isEqualInstallment()
&& loan.getLoanProductRelatedDetail().getInterestMethod().isDecliningBalnce()) {
decimalValue = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.installmentAmountParamName, arrayElement,
locale);
decimalValueVariationType = LoanTermVariationType.EMI_AMOUNT;
} else {
decimalValue = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.principalParamName, arrayElement, locale);
decimalValueVariationType = LoanTermVariationType.PRINCIPAL_AMOUNT;
}
LocalDate duedateLocalDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.dueDateParamName, arrayElement,
dateFormat, locale);
Date dueDate = duedateLocalDate.toDate();
LocalDate modifiedDuedateLocalDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.modifiedDueDateParamName,
arrayElement, dateFormat, locale);
Date modifiedDuedate = null;
if (modifiedDuedateLocalDate != null) {
modifiedDuedate = modifiedDuedateLocalDate.toDate();
}
boolean isSpecificToInstallment = true;
if (isInsertInstallment) {
LoanTermVariations data = new LoanTermVariations(LoanTermVariationType.INSERT_INSTALLMENT.getValue(), dueDate,
decimalValue, modifiedDuedate, isSpecificToInstallment, loan);
loanTermVariations.add(data);
} else if (isDeleteInstallment) {
LoanTermVariations data = new LoanTermVariations(LoanTermVariationType.DELETE_INSTALLMENT.getValue(), dueDate,
decimalValue, modifiedDuedate, isSpecificToInstallment, loan);
loanTermVariations.add(data);
} else {
if (modifiedDuedate != null) {
BigDecimal amountData = null;
LoanTermVariations data = new LoanTermVariations(LoanTermVariationType.DUE_DATE.getValue(), dueDate, amountData,
modifiedDuedate, isSpecificToInstallment, loan);
loanTermVariations.add(data);
}
if (decimalValue != null) {
if (modifiedDuedate == null) {
modifiedDuedate = dueDate;
}
Date date = null;
LoanTermVariations data = new LoanTermVariations(decimalValueVariationType.getValue(), modifiedDuedate, decimalValue,
date, isSpecificToInstallment, loan);
loanTermVariations.add(data);
}
}
}
}
private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate disbursementDate, final WorkingDays workingDays) {
if (!WorkingDaysUtil.isWorkingDay(workingDays, disbursementDate)) {
final String errorMessage = "The expected disbursement date cannot be on a non working day";
@@ -14,4 +14,6 @@ public interface LoanScheduleCalculationPlatformService {
LoanScheduleModel calculateLoanSchedule(JsonQuery query, Boolean validateParams);
void updateFutureSchedule(LoanScheduleData loanScheduleData, Long loanId);
LoanScheduleData generateLoanScheduleForVariableInstallmentRequest(Long loanId, String json);
}
@@ -8,7 +8,10 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -18,10 +21,12 @@ import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.organisation.monetary.data.CurrencyData;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
@@ -32,6 +37,7 @@ import org.mifosplatform.portfolio.floatingrates.exception.FloatingRateNotFoundE
import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -68,6 +74,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final ConfigurationDomainService configurationDomainService;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final CurrencyReadPlatformService currencyReadPlatformService;
@Autowired
public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer,
@@ -78,7 +85,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
final LoanAccountDomainService accountDomainService, final CalendarInstanceRepository calendarInstanceRepository,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final ConfigurationDomainService configurationDomainService,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
final CurrencyReadPlatformService currencyReadPlatformService) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanScheduleAssembler = loanScheduleAssembler;
this.fromJsonHelper = fromJsonHelper;
@@ -93,6 +101,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.configurationDomainService = configurationDomainService;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.currencyReadPlatformService = currencyReadPlatformService;
}
@Override
@@ -162,18 +171,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
}
}
}
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
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);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance, floatingRateDTO);
LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(loan);
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency,
today, loanApplicationTerms, loan.charges(), loan.getOfficeId(),
loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor);
@@ -218,21 +216,99 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
loanScheduleData.updateFuturePeriods(futureInstallments);
}
@Override
public LoanScheduleData generateLoanScheduleForVariableInstallmentRequest(Long loanId, final String json) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
this.loanScheduleAssembler.assempleVariableScheduleFrom(loan, json);
return constructLoanScheduleData(loan);
}
private LoanScheduleData constructLoanScheduleData(Loan loan) {
Collection<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
final List<LoanSchedulePeriodData> installmentData = new ArrayList<>();
final MonetaryCurrency currency = loan.getCurrency();
Money outstanding = loan.getPrincpal();
Set<LoanDisbursementDetails> disbursementDetails = new HashSet<>();
if (loan.isMultiDisburmentLoan()) {
disbursementDetails = loan.getDisbursementDetails();
outstanding = outstanding.zero();
}
Money principal = outstanding;
Iterator<LoanDisbursementDetails> disbursementItr = disbursementDetails.iterator();
LoanDisbursementDetails loanDisbursementDetails = null;
if (disbursementItr.hasNext()) {
loanDisbursementDetails = disbursementItr.next();
}
Money totalInterest = principal.zero();
Money totalCharge = principal.zero();
Money totalPenalty = principal.zero();
for (LoanRepaymentScheduleInstallment installment : installments) {
if (loanDisbursementDetails != null
&& !loanDisbursementDetails.expectedDisbursementDateAsLocalDate().isAfter(installment.getDueDate())) {
outstanding = outstanding.plus(loanDisbursementDetails.principal());
principal = principal.plus(loanDisbursementDetails.principal());
if (disbursementItr.hasNext()) {
loanDisbursementDetails = disbursementItr.next();
} else {
loanDisbursementDetails = null;
}
}
outstanding = outstanding.minus(installment.getPrincipal(currency));
LoanSchedulePeriodData loanSchedulePeriodData = LoanSchedulePeriodData.repaymentOnlyPeriod(installment.getInstallmentNumber(),
installment.getFromDate(), installment.getDueDate(), installment.getPrincipal(currency).getAmount(),
outstanding.getAmount(), installment.getInterestCharged(currency).getAmount(),
installment.getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(),
installment.getDue(currency).getAmount());
installmentData.add(loanSchedulePeriodData);
totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
totalCharge = totalCharge.plus(installment.getFeeChargesCharged(currency));
totalPenalty = totalPenalty.plus(installment.getPenaltyChargesCharged(currency));
}
CurrencyData currencyData = this.currencyReadPlatformService.retrieveCurrency(currency.getCode());
LoanScheduleData scheduleData = new LoanScheduleData(currencyData, installmentData, loan.getLoanRepaymentScheduleDetail()
.getNumberOfRepayments(), principal.getAmount(), principal.getAmount(), totalInterest.getAmount(), totalCharge.getAmount(),
totalPenalty.getAmount(), principal.plus(totalCharge).plus(totalInterest).plus(totalPenalty).getAmount());
return scheduleData;
}
private LoanApplicationTerms constructLoanApplicationTerms(final Loan loan) {
MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
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);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance, floatingRateDTO);
return loanApplicationTerms;
}
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
FloatingRateDTO floatingRateDTO = null;
if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
BigDecimal interestRateDiff = loan.getInterestRateDifferential();
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try{
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
}catch(final FloatingRateNotFoundException ex){
// Do not do anything
try {
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
} catch (final FloatingRateNotFoundException ex) {
// Do not do anything
}
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
}
@@ -0,0 +1,16 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
public interface LoanScheduleWritePlatformService {
public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command);
CommandProcessingResult deleteLoanScheduleVariations(final Long loanId);
}
@@ -0,0 +1,100 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariations;
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
import org.mifosplatform.portfolio.loanaccount.service.LoanUtilService;
import org.mifosplatform.useradministration.domain.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoanScheduleWritePlatformServiceImpl implements LoanScheduleWritePlatformService {
private final LoanAccountDomainService loanAccountDomainService;
private final LoanAssembler loanAssembler;
private final LoanScheduleAssembler loanScheduleAssembler;
private final PlatformSecurityContext context;
private final LoanUtilService loanUtilService;
@Autowired
public LoanScheduleWritePlatformServiceImpl(final LoanAccountDomainService loanAccountDomainService,
final LoanScheduleAssembler loanScheduleAssembler, final LoanAssembler loanAssembler, final PlatformSecurityContext context,
final LoanUtilService loanUtilService) {
this.loanAccountDomainService = loanAccountDomainService;
this.loanScheduleAssembler = loanScheduleAssembler;
this.loanAssembler = loanAssembler;
this.context = context;
this.loanUtilService = loanUtilService;
}
@Override
public CommandProcessingResult addLoanScheduleVariations(final Long loanId, final JsonCommand command) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
Map<Long, LoanTermVariations> loanTermVariations = new HashMap<>();
for (LoanTermVariations termVariations : loan.getLoanTermVariations()) {
loanTermVariations.put(termVariations.getId(), termVariations);
}
this.loanScheduleAssembler.assempleVariableScheduleFrom(loan, command.json());
this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan);
final Map<String, Object> changes = new HashMap<>();
List<LoanTermVariationsData> newVariationsData = new ArrayList<>();
List<LoanTermVariations> modifiedVariations = loan.getLoanTermVariations();
for (LoanTermVariations termVariations : modifiedVariations) {
if (loanTermVariations.containsKey(termVariations.getId())) {
loanTermVariations.remove(termVariations.getId());
} else {
newVariationsData.add(termVariations.toData());
}
}
if (!loanTermVariations.isEmpty()) {
changes.put("removedVariations", loanTermVariations.keySet());
}
changes.put("loanTermVariations", newVariationsData);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withLoanId(loanId) //
.with(changes) //
.build();
}
@Override
public CommandProcessingResult deleteLoanScheduleVariations(final Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
List<LoanTermVariations> variations = loan.getLoanTermVariations();
List<Long> deletedVariations = new ArrayList<>(variations.size());
for (LoanTermVariations loanTermVariations : variations) {
deletedVariations.add(loanTermVariations.getId());
}
final Map<String, Object> changes = new HashMap<>();
changes.put("removedEntityIds", deletedVariations);
loan.getLoanTermVariations().clear();
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan);
AppUser currentUser = this.context.getAuthenticatedUserIfPresent();
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
this.loanAccountDomainService.saveLoanWithDataIntegrityViolationChecks(loan);
return new CommandProcessingResultBuilder() //
.withLoanId(loanId) //
.with(changes) //
.build();
}
}
@@ -222,18 +222,22 @@ public final class LoanApplicationCommandFromApiJsonHelper {
"interestRatePerPeriod param is not supported, selected Loan Product is linked with floating interest rate.");
}
if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)){
final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(
LoanApiConstants.isFloatingInterestRate, element);
if(isFloatingInterestRate != null && isFloatingInterestRate && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()){
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode("true.not.supported.for.selected.loanproduct",
"isFloatingInterestRate value of true not supported for selected Loan Product.");
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate,
element);
if (isFloatingInterestRate != null && isFloatingInterestRate
&& !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) {
baseDataValidator
.reset()
.parameter(LoanApiConstants.isFloatingInterestRate)
.failWithCode("true.not.supported.for.selected.loanproduct",
"isFloatingInterestRate value of true not supported for selected Loan Product.");
}
} else {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false);
}
if (interestType != null && interestType == InterestMethod.FLAT.getValue()) {
if (interestType != null && interestType.equals(InterestMethod.FLAT.getValue())) {
baseDataValidator
.reset()
.parameter(interestTypeParameterName)
@@ -595,15 +599,17 @@ public final class LoanApplicationCommandFromApiJsonHelper {
}
Boolean isFloatingInterestRate = existingLoanApplication.getIsFloatingInterestRate();
if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)){
isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, element);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, element);
atLeastOneParameterPassedForUpdate = true;
}
if(isFloatingInterestRate != null){
if(isFloatingInterestRate
&& !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()){
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode("true.not.supported.for.selected.loanproduct",
"isFloatingInterestRate value of true not supported for selected Loan Product.");
if (isFloatingInterestRate != null) {
if (isFloatingInterestRate && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) {
baseDataValidator
.reset()
.parameter(LoanApiConstants.isFloatingInterestRate)
.failWithCode("true.not.supported.for.selected.loanproduct",
"isFloatingInterestRate value of true not supported for selected Loan Product.");
}
} else {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false);
@@ -612,7 +618,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
if (interestType == null) {
interestType = existingLoanApplication.getLoanProductRelatedDetail().getInterestMethod().getValue();
}
if (interestType != null && interestType == InterestMethod.FLAT.getValue()) {
if (interestType != null && interestType.equals(InterestMethod.FLAT.getValue())) {
baseDataValidator
.reset()
.parameter(interestTypeParameterName)
@@ -731,6 +737,10 @@ public final class LoanApplicationCommandFromApiJsonHelper {
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed(
repaymentsStartingFromDateParameterName, element);
baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull();
if (!existingLoanApplication.getLoanTermVariations().isEmpty()) {
baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate)
.failWithCode("invalid.due.to.variable.installments");
}
}
final String submittedOnDateParameterName = "submittedOnDate";
@@ -0,0 +1,151 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.serialization;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
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.exception.UnsupportedParameterException;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
@Component
public class VariableLoanScheduleFromApiJsonValidator {
final Set<String> variableSchedulesupportedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.exceptionParamName,
LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName));
final Set<String> variableSchedulesupportedArrayParameters = new HashSet<>(Arrays.asList(
LoanApiConstants.modifiedinstallmentsParamName, LoanApiConstants.newinstallmentsParamName,
LoanApiConstants.deletedinstallmentsParamName));
final Set<String> variableScheduleModifiedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.dueDateParamName,
LoanApiConstants.modifiedDueDateParamName, LoanApiConstants.principalParamName, LoanApiConstants.installmentAmountParamName));
final Set<String> variableScheduleNewInstallmentParameters = new HashSet<>(Arrays.asList(LoanApiConstants.dueDateParamName,
LoanApiConstants.principalParamName, LoanApiConstants.installmentAmountParamName));
final Set<String> variableScheduleDeleteInstallmentParameters = new HashSet<>(Arrays.asList(LoanApiConstants.dueDateParamName));
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public VariableLoanScheduleFromApiJsonValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateSchedule(final String json, final Loan loan) {
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.variableSchedulesupportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
if (!loan.isSubmittedAndPendingApproval()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("account.is.not.submitted.and.pending.state",
"Loan is not in submited state");
throw new PlatformApiDataValidationException(dataValidationErrors);
}
final JsonElement element = this.fromApiJsonHelper.parse(json);
if (loan.loanProduct().allowVariabeInstallments()) {
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.exceptionParamName, element)) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
final JsonObject exceptionObject = topLevelJsonElement.getAsJsonObject(LoanApiConstants.exceptionParamName);
this.fromApiJsonHelper.checkForUnsupportedParameters(exceptionObject, this.variableSchedulesupportedArrayParameters);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.modifiedinstallmentsParamName, exceptionObject)
&& exceptionObject.get(LoanApiConstants.modifiedinstallmentsParamName).isJsonArray()) {
final JsonArray modificationsArray = exceptionObject.get(LoanApiConstants.modifiedinstallmentsParamName)
.getAsJsonArray();
validateLoanTermVariations(loan, baseDataValidator, dateFormat, locale, modificationsArray,
this.variableScheduleModifiedParameters, LoanApiConstants.modifiedinstallmentsParamName);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.newinstallmentsParamName, exceptionObject)
&& exceptionObject.get(LoanApiConstants.newinstallmentsParamName).isJsonArray()) {
final JsonArray array = exceptionObject.get(LoanApiConstants.newinstallmentsParamName).getAsJsonArray();
validateLoanTermVariations(loan, baseDataValidator, dateFormat, locale, array,
this.variableScheduleNewInstallmentParameters, LoanApiConstants.newinstallmentsParamName);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.deletedinstallmentsParamName, exceptionObject)
&& exceptionObject.get(LoanApiConstants.deletedinstallmentsParamName).isJsonArray()) {
final JsonArray array = exceptionObject.get(LoanApiConstants.deletedinstallmentsParamName).getAsJsonArray();
validateLoanTermVariations(loan, baseDataValidator, dateFormat, locale, array,
this.variableScheduleDeleteInstallmentParameters, LoanApiConstants.deletedinstallmentsParamName);
}
}
} else {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.not.supported",
"Loan schedule modification not allowed");
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
private void validateLoanTermVariations(final Loan loan, final DataValidatorBuilder baseDataValidator, final String dateFormat,
final Locale locale, final JsonArray modificationsArray, final Set<String> supportParams, final String arrayName) {
for (int i = 1; i <= modificationsArray.size(); i++) {
final JsonObject arrayElement = modificationsArray.get(i - 1).getAsJsonObject();
this.fromApiJsonHelper.checkForUnsupportedParameters(arrayElement, supportParams);
final BigDecimal installmentAmount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.installmentAmountParamName,
arrayElement, locale);
baseDataValidator.reset().parameter(arrayName).parameterAtIndexArray(LoanApiConstants.installmentAmountParamName, i)
.value(installmentAmount).positiveAmount();
final BigDecimal principalAmount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.principalParamName,
arrayElement, locale);
baseDataValidator.reset().parameter(arrayName).parameterAtIndexArray(LoanApiConstants.principalParamName, i)
.value(principalAmount).zeroOrPositiveAmount();
if (loan.getLoanProductRelatedDetail().getInterestMethod().isDecliningBalnce()
&& loan.getLoanProductRelatedDetail().getAmortizationMethod().isEqualInstallment() && principalAmount != null) {
List<String> unsupportedParams = new ArrayList<>(1);
unsupportedParams.add(LoanApiConstants.principalParamName);
throw new UnsupportedParameterException(unsupportedParams);
} else if ((!loan.getLoanProductRelatedDetail().getInterestMethod().isDecliningBalnce() || loan.getLoanProductRelatedDetail()
.getAmortizationMethod().isEqualPrincipal())
&& installmentAmount != null) {
List<String> unsupportedParams = new ArrayList<>(1);
unsupportedParams.add(LoanApiConstants.installmentAmountParamName);
throw new UnsupportedParameterException(unsupportedParams);
}
LocalDate duedate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.dueDateParamName, arrayElement, dateFormat,
locale);
baseDataValidator.reset().parameter(arrayName).parameterAtIndexArray(LoanApiConstants.dueDateParamName, i).value(duedate)
.notNull().validateDateAfter(loan.getExpectedDisbursedOnLocalDate());
LocalDate modifiedDuedate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.modifiedDueDateParamName,
arrayElement, dateFormat, locale);
baseDataValidator.reset().parameter(arrayName).parameterAtIndexArray(LoanApiConstants.modifiedDueDateParamName, i)
.value(modifiedDuedate).validateDateAfter(loan.getExpectedDisbursedOnLocalDate());
if (arrayName.equals(LoanApiConstants.modifiedinstallmentsParamName) && modifiedDuedate == null && installmentAmount == null
&& principalAmount == null) {
baseDataValidator.reset().parameter(arrayName).failWithCode("variation.required", "At least one vario");
}
}
}
}
@@ -476,15 +476,15 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
loanId); }
final String productIdParamName = "productId";
LoanProduct newLoanProduct = null;
LoanProduct newLoanProduct = null;
if (command.isChangeInLongParameterNamed(productIdParamName, existingLoanApplication.loanProduct().getId())) {
final Long productId = command.longValueOfParameterNamed(productIdParamName);
newLoanProduct = this.loanProductRepository.findOne(productId);
if (newLoanProduct == null) { throw new LoanProductNotFoundException(productId); }
}
LoanProduct loanProductForValidations = newLoanProduct == null? existingLoanApplication.loanProduct(): newLoanProduct;
LoanProduct loanProductForValidations = newLoanProduct == null ? existingLoanApplication.loanProduct() : newLoanProduct;
this.fromApiJsonDeserializer.validateForModify(command.json(), loanProductForValidations, existingLoanApplication);
checkClientOrGroupActive(existingLoanApplication);
@@ -495,21 +495,23 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
LoanChargeData chargeData = new LoanChargeData(charge.getId(), charge.getDueLocalDate(), charge.amountOrPercentage());
chargesMap.put(charge.getId(), chargeData);
}
Set<LoanDisbursementDetails> disbursementDetails = this.loanAssembler.fetchDisbursementData(command.parsedJson().getAsJsonObject());
Set<LoanDisbursementDetails> disbursementDetails = this.loanAssembler.fetchDisbursementData(command.parsedJson()
.getAsJsonObject());
/**
* Stores all charges which are passed in during modify loan
* application
**/
final Set<LoanCharge> possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),disbursementDetails);
final Set<LoanCharge> possiblyModifedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),
disbursementDetails);
/** Boolean determines if any charge has been modified **/
boolean isChargeModified = false;
Set<Charge> newTrancheChages = this.loanChargeAssembler.getNewLoanTrancheCharges(command.parsedJson()) ;
Set<Charge> newTrancheChages = this.loanChargeAssembler.getNewLoanTrancheCharges(command.parsedJson());
for (Charge charge : newTrancheChages) {
existingLoanApplication.addTrancheLoanCharge(charge) ;
existingLoanApplication.addTrancheLoanCharge(charge);
}
/**
* If there are any charges already present, which are now not
* passed in as a part of the request, deem the charges as modified
@@ -579,19 +581,19 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, newLoanProduct.getId());
} else if (groupId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(),
newLoanProduct.getId());
newLoanProduct.getId());
}
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
newLoanProduct, cycleNumber);
newLoanProduct, cycleNumber);
} else {
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
newLoanProduct);
newLoanProduct);
}
if(newLoanProduct.isLinkedToFloatingInterestRate()){
existingLoanApplication.getLoanProductRelatedDetail().updateForFloatingInterestRates();
}else{
existingLoanApplication.setInterestRateDifferential(null);
existingLoanApplication.setIsFloatingInterestRate(null);
if (newLoanProduct.isLinkedToFloatingInterestRate()) {
existingLoanApplication.getLoanProductRelatedDetail().updateForFloatingInterestRates();
} else {
existingLoanApplication.setInterestRateDifferential(null);
existingLoanApplication.setIsFloatingInterestRate(null);
}
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
}
@@ -644,7 +646,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
if (changes.containsKey(chargesParamName)) {
existingLoanApplication.updateLoanCharges(possiblyModifedLoanCharges);
}
if (changes.containsKey("recalculateLoanSchedule")) {
changes.remove("recalculateLoanSchedule");
@@ -656,7 +658,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
existingLoanApplication.recalculateAllCharges();
}
this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(),
existingLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(),
productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(),
@@ -723,7 +724,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
isModified = true;
} else {
final SavingsAccount savingsAccount = accountAssociations.linkedSavingsAccount();
if (savingsAccount == null || savingsAccount.getId() != savingsAccountId) {
if (savingsAccount == null || !savingsAccount.getId().equals(savingsAccountId)) {
isModified = true;
}
}
@@ -891,7 +892,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
// If loan approved amount less than loan demanded amount, then need
// to recompute the schedule
if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey("recalculateLoanSchedule")) {
if (changes.containsKey(LoanApiConstants.approvedLoanAmountParameterName) || changes.containsKey("recalculateLoanSchedule")
|| changes.containsKey("expectedDisbursementDate")) {
ScheduleGeneratorDTO scheduleGeneratorDTO = loanAccountDomainService.buildScheduleGeneratorDTO(loan);
loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser);
}
@@ -1082,7 +1084,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment);
}
}
}
this.loanRepository.saveAndFlush(loan);
} catch (final DataIntegrityViolationException e) {
@@ -141,7 +141,7 @@ public class LoanChargeAssembler {
}
}
if (chargeDefinition.getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue()) {
if ( ChargeTimeType.DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
if (chargeDefinition.isPercentageOfApprovedAmount()
@@ -164,10 +164,10 @@ public class LoanChargeAssembler {
}
}
}
} else if (chargeDefinition.getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()) {
} else if (ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
for (LoanDisbursementDetails disbursementDetail : disbursementDetails) {
if (chargeDefinition.getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue()) {
if (ChargeTimeType.TRANCHE_DISBURSEMENT.getValue().equals(chargeDefinition.getChargeTimeType())) {
final LoanCharge loanCharge = LoanCharge.createNewWithoutLoan(chargeDefinition,
disbursementDetail.principal(), amount, chargeTime, chargeCalculation,
disbursementDetail.expectedDisbursementDateAsLocalDate(), chargePaymentModeEnum,
@@ -17,11 +17,8 @@ import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
@@ -66,9 +63,9 @@ import org.mifosplatform.portfolio.client.domain.ClientEnumerations;
import org.mifosplatform.portfolio.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.CommonEnumerations;
import org.mifosplatform.portfolio.floatingrates.data.InterestRatePeriodData;
import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO;
import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.mifosplatform.portfolio.floatingrates.data.InterestRatePeriodData;
import org.mifosplatform.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.mifosplatform.portfolio.fund.data.FundData;
@@ -559,6 +556,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " l.loan_type_enum as loanType, l.loanpurpose_cv_id as loanPurposeId, cv.code_value as loanPurposeName,"
+ " lp.id as loanProductId, lp.name as loanProductName, lp.description as loanProductDescription,"
+ " lp.is_linked_to_floating_interest_rates as isLoanProductLinkedToFloatingRate, "
+ " lp.allow_variabe_installments as isvariableInstallmentsAllowed, "
+ " lp.allow_multiple_disbursals as multiDisburseLoan,"
+ " lp.can_define_fixed_emi_amount as canDefineInstallmentAmount,"
+ " c.id as clientId, c.display_name as clientName, c.office_id as clientOfficeId,"
@@ -630,7 +628,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " lir.compounding_freqency_date as compoundingFrequencyDate, "
+ " l.is_floating_interest_rate as isFloatingInterestRate, "
+ " l.interest_rate_differential as interestRateDifferential, "
+ " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement "
+ " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement, "
+ " lpvi.minimum_gap as minimuminstallmentgap, lpvi.maximum_gap as maximuminstallmentgap "
+ " from m_loan l" //
+ " join m_product_loan lp on lp.id = l.product_id" //
+ " left join m_loan_recalculation_details lir on lir.loan_id = l.id "
@@ -647,7 +646,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
+ " left join m_appuser dbu on dbu.id = l.disbursedon_userid"
+ " left join m_appuser cbu on cbu.id = l.closedon_userid"
+ " left join m_code_value cv on cv.id = l.loanpurpose_cv_id"
+ " left join ref_loan_transaction_processing_strategy lps on lps.id = l.loan_transaction_strategy_id";
+ " left join ref_loan_transaction_processing_strategy lps on lps.id = l.loan_transaction_strategy_id"
+ " left join m_product_loan_variable_installment_config lpvi on lpvi.loan_product_id = l.product_id";
}
@Override
@@ -735,6 +736,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate");
final Boolean isvariableInstallmentsAllowed = rs.getBoolean("isvariableInstallmentsAllowed");
final Integer minimumGap = rs.getInt("minimuminstallmentgap");
final Integer maximumGap = rs.getInt("maximuminstallmentgap");
final LoanApplicationTimelineData timeline = new LoanApplicationTimelineData(submittedOnDate, submittedByUsername,
submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname,
withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername,
@@ -907,17 +912,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName,
loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid,
inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType,
repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName,
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, expectedFirstRepaymentOnDate,
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline,
loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter,
multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing,
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData,
createStandingInstructionAtDisbursement);
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal,
approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType,
transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType,
annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting,
loanCounter, loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance,
inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap,
maximumGap);
}
}
@@ -1468,30 +1474,21 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
}
@Override
public Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallments(final Long penaltyWaitPeriod, final Boolean backdatePenalties) {
public Collection<OverdueLoanScheduleData> retrieveAllLoansWithOverdueInstallments(final Long penaltyWaitPeriod,
final Boolean backdatePenalties) {
final MusoniOverdueLoanScheduleMapper rm = new MusoniOverdueLoanScheduleMapper();
final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder
.append("select ")
.append(rm.schema())
.append(" where DATE_SUB(CURDATE(),INTERVAL ? DAY) > ls.duedate ")
.append( " and ls.completed_derived <> 1 and mc.charge_applies_to_enum =1 ")
sqlBuilder.append("select ").append(rm.schema()).append(" where DATE_SUB(CURDATE(),INTERVAL ? DAY) > ls.duedate ")
.append(" and ls.completed_derived <> 1 and mc.charge_applies_to_enum =1 ")
.append(" and mc.charge_time_enum = 9 and ml.loan_status_id = 300 ");
if (backdatePenalties) { return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { penaltyWaitPeriod }); }
// Only apply for duedate = yesterday (so that we don't apply
// penalties on the duedate itself)
sqlBuilder.append(" and ls.duedate >= DATE_SUB(CURDATE(),INTERVAL (? + 1) DAY)");
if(backdatePenalties)
{
return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { penaltyWaitPeriod });
}
else
{
// Only apply for duedate = yesterday (so that we don't apply penalties on the duedate itself)
sqlBuilder.append(" and ls.duedate >= DATE_SUB(CURDATE(),INTERVAL (? + 1) DAY)");
return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { penaltyWaitPeriod, penaltyWaitPeriod });
}
return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { penaltyWaitPeriod, penaltyWaitPeriod });
}
@@ -1558,7 +1555,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private static final class LoanTermVariationsMapper implements RowMapper<LoanTermVariationsData> {
public String schema() {
return "tv.id as id,tv.applicable_from as variationApplicableFrom,tv.term_value as termValue "
return "tv.id as id,tv.applicable_date as variationApplicableFrom,tv.decimal_value as decimalValue, tv.date_value as dateValue, tv.is_specific_to_installment as isSpecificToInstallment "
+ "from m_loan_term_variations tv";
}
@@ -1566,10 +1563,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
public LoanTermVariationsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final LocalDate variationApplicableFrom = JdbcSupport.getLocalDate(rs, "variationApplicableFrom");
final BigDecimal termValue = rs.getBigDecimal("termValue");
final BigDecimal decimalValue = rs.getBigDecimal("decimalValue");
final LocalDate dateValue = JdbcSupport.getLocalDate(rs, "dateValue");
final boolean isSpecificToInstallment = rs.getBoolean("isSpecificToInstallment");
final LoanTermVariationsData loanTermVariationsData = new LoanTermVariationsData(id,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), variationApplicableFrom, termValue);
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), variationApplicableFrom, decimalValue, dateValue,
isSpecificToInstallment);
return loanTermVariationsData;
}
@@ -1993,11 +1993,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
BigDecimal interestRateDiff = loan.getInterestRateDifferential();
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try{
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
}catch(final FloatingRateNotFoundException ex){
// Do not do anything
try {
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
} catch (final FloatingRateNotFoundException ex) {
// Do not do anything
}
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
@@ -18,7 +18,6 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation
import org.mifosplatform.infrastructure.jobs.annotation.CronTarget;
import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException;
import org.mifosplatform.infrastructure.jobs.service.JobName;
import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,16 +31,13 @@ public class LoanSchedularServiceImpl implements LoanSchedularService {
private final ConfigurationDomainService configurationDomainService;
private final LoanReadPlatformService loanReadPlatformService;
private final LoanWritePlatformService loanWritePlatformService;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired
public LoanSchedularServiceImpl(final ConfigurationDomainService configurationDomainService,
final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService) {
this.configurationDomainService = configurationDomainService;
this.loanReadPlatformService = loanReadPlatformService;
this.loanWritePlatformService = loanWritePlatformService;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
}
@Override
@@ -0,0 +1,140 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
import org.mifosplatform.organisation.holiday.domain.HolidayStatusType;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO;
import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.mifosplatform.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class LoanUtilService {
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final CalendarInstanceRepository calendarInstanceRepository;
private final ConfigurationDomainService configurationDomainService;
private final HolidayRepository holidayRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired
public LoanUtilService(final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService,
final HolidayRepository holidayRepository, final WorkingDaysRepositoryWrapper workingDaysRepository,
final LoanScheduleGeneratorFactory loanScheduleFactory, final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.calendarInstanceRepository = calendarInstanceRepository;
this.configurationDomainService = configurationDomainService;
this.holidayRepository = holidayRepository;
this.workingDaysRepository = workingDaysRepository;
this.loanScheduleFactory = loanScheduleFactory;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
}
public LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan,
final CalendarInstance calendarInstance) {
final Calendar calendar = calendarInstance == null ? null : calendarInstance.getCalendar();
LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
if (calendar != null) {// sync repayments
// TODO: AA - user provided first repayment date takes precedence
// over recalculated meeting date
if (calculatedRepaymentsStartingFromDate == null) {
// FIXME: AA - Possibility of having next meeting date
// immediately after disbursement date,
// need to have minimum number of days gap between disbursement
// and first repayment date.
final LoanProductRelatedDetail repaymentScheduleDetails = loan.repaymentScheduleDetail();
if (repaymentScheduleDetails != null) {// Not expecting to be
// null
final Integer repayEvery = repaymentScheduleDetails.getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
.getRepaymentPeriodFrequencyType());
calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate,
repayEvery, frequency);
}
}
}
return calculatedRepaymentsStartingFromDate;
}
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan) {
final MonetaryCurrency currency = loan.getCurrency();
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
LocalDate calculatedRepaymentsStartingFromDate = this.getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
.getExpectedDisbursedOnLocalDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
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_REST_DETAIL.getValue());
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
}
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, floatingRateDTO);
return scheduleGeneratorDTO;
}
private FloatingRateDTO constructFloatingRateDTO(final Loan loan) {
FloatingRateDTO floatingRateDTO = null;
if (loan.loanProduct().isLinkedToFloatingInterestRate()) {
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
BigDecimal interestRateDiff = loan.getInterestRateDifferential();
List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try {
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
} catch (final FloatingRateNotFoundException ex) {
// Do not do anything
}
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
}
@@ -2441,6 +2441,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
@Override
@Transactional
public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDatas) {
@@ -102,5 +102,12 @@ public interface LoanProductConstants {
public static final String inArrearsToleranceParamName = "inArrearsTolerance";
public static final String repaymentEveryParamName = "repaymentEvery";
public static final String graceOnPrincipalAndInterestPaymentParamName = "graceOnPrincipalAndInterestPayment";
//Variable Installments Settings
public static final String allowVariableInstallmentsParamName = "allowVariableInstallments" ;
public static final String minimumGapBetweenInstallments = "minimumGap" ;
public static final String maximumGapBetweenInstallments = "maximumGap" ;
}
@@ -68,7 +68,7 @@ public class LoanProductData {
private final BigDecimal maxInterestRatePerPeriod;
private final EnumOptionData interestRateFrequencyType;
private final BigDecimal annualInterestRate;
private final boolean isLinkedToFloatingInterestRates;
private final Integer floatingRateId;
private final String floatingRateName;
@@ -78,6 +78,11 @@ public class LoanProductData {
private final BigDecimal maxDifferentialLendingRate;
private final boolean isFloatingInterestRateCalculationAllowed;
// Variable Installments Settings
private final boolean allowVariableInstallments;
private final Integer minimumGap;
private final Integer maximumGap;
// settings
private final EnumOptionData amortizationType;
private final EnumOptionData interestType;
@@ -185,6 +190,9 @@ public class LoanProductData {
final BigDecimal defaultDifferentialLendingRate = null;
final BigDecimal maxDifferentialLendingRate = null;
final boolean isFloatingInterestRateCalculationAllowed = false;
final boolean isVariableInstallmentsAllowed = false;
final Integer minimumGap = null;
final Integer maximumGap = null;
final EnumOptionData repaymentFrequencyType = null;
final EnumOptionData interestRateFrequencyType = null;
final EnumOptionData amortizationType = null;
@@ -238,8 +246,8 @@ public class LoanProductData {
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed);
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap);
}
@@ -266,6 +274,9 @@ public class LoanProductData {
final BigDecimal defaultDifferentialLendingRate = null;
final BigDecimal maxDifferentialLendingRate = null;
final boolean isFloatingInterestRateCalculationAllowed = false;
final boolean isVariableInstallmentsAllowed = false;
final Integer minimumGap = null;
final Integer maximumGap = null;
final EnumOptionData repaymentFrequencyType = null;
final EnumOptionData interestRateFrequencyType = null;
final EnumOptionData amortizationType = null;
@@ -321,8 +332,8 @@ public class LoanProductData {
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed);
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap);
}
@@ -353,6 +364,9 @@ public class LoanProductData {
final BigDecimal defaultDifferentialLendingRate = null;
final BigDecimal maxDifferentialLendingRate = null;
final boolean isFloatingInterestRateCalculationAllowed = false;
final boolean isVariableInstallmentsAllowed = false;
final Integer minimumGap = null;
final Integer maximumGap = null;
final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(PeriodFrequencyType.MONTHS);
final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(PeriodFrequencyType.MONTHS);
final EnumOptionData amortizationType = LoanEnumerations.amortizationType(AmortizationMethod.EQUAL_INSTALLMENTS);
@@ -411,9 +425,9 @@ public class LoanProductData {
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf,
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed);
loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName,
interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap);
}
@@ -449,9 +463,10 @@ public class LoanProductData {
final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstallment,
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, boolean canDefineInstallmentAmount,
Integer installmentAmountInMultiplesOf, LoanProductConfigurableAttributes allowAttributeOverrides,
boolean isLinkedToFloatingInterestRates, Integer floatingRateId, String floatingRateName,
BigDecimal interestRateDifferential, BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate,
BigDecimal maxDifferentialLendingRate, boolean isFloatingInterestRateCalculationAllowed) {
boolean isLinkedToFloatingInterestRates, Integer floatingRateId, String floatingRateName, BigDecimal interestRateDifferential,
BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal maxDifferentialLendingRate,
boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed,
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -480,6 +495,9 @@ public class LoanProductData {
this.defaultDifferentialLendingRate = defaultDifferentialLendingRate;
this.maxDifferentialLendingRate = maxDifferentialLendingRate;
this.isFloatingInterestRateCalculationAllowed = isFloatingInterestRateCalculationAllowed;
this.allowVariableInstallments = isVariableInstallmentsAllowed;
this.minimumGap = minimumGapBetweenInstallments;
this.maximumGap = maximumGapBetweenInstallments;
this.repaymentFrequencyType = repaymentFrequencyType;
this.interestRateFrequencyType = interestRateFrequencyType;
this.amortizationType = amortizationType;
@@ -587,6 +605,9 @@ public class LoanProductData {
this.defaultDifferentialLendingRate = productData.defaultDifferentialLendingRate;
this.maxDifferentialLendingRate = productData.maxDifferentialLendingRate;
this.isFloatingInterestRateCalculationAllowed = productData.isFloatingInterestRateCalculationAllowed;
this.allowVariableInstallments = productData.allowVariableInstallments;
this.minimumGap = productData.minimumGap;
this.maximumGap = productData.maximumGap;
this.repaymentFrequencyType = productData.repaymentFrequencyType;
this.interestRateFrequencyType = productData.interestRateFrequencyType;
this.amortizationType = productData.amortizationType;
@@ -950,25 +971,35 @@ public class LoanProductData {
this.allowAttributeOverrides = loanProductConfigurableAttributes;
}
public boolean isLinkedToFloatingInterestRates() {
return this.isLinkedToFloatingInterestRates;
}
public boolean isLinkedToFloatingInterestRates() {
return this.isLinkedToFloatingInterestRates;
}
public BigDecimal getMinDifferentialLendingRate() {
return this.minDifferentialLendingRate;
}
public BigDecimal getMinDifferentialLendingRate() {
return this.minDifferentialLendingRate;
}
public BigDecimal getDefaultDifferentialLendingRate() {
return this.defaultDifferentialLendingRate;
}
public BigDecimal getDefaultDifferentialLendingRate() {
return this.defaultDifferentialLendingRate;
}
public BigDecimal getMaxDifferentialLendingRate() {
return this.maxDifferentialLendingRate;
}
public BigDecimal getMaxDifferentialLendingRate() {
return this.maxDifferentialLendingRate;
}
public boolean isFloatingInterestRateCalculationAllowed() {
return this.isFloatingInterestRateCalculationAllowed;
}
public boolean isFloatingInterestRateCalculationAllowed() {
return this.isFloatingInterestRateCalculationAllowed;
}
public boolean isVariableInstallmentsAllowed() {
return this.allowVariableInstallments;
}
public Integer getMinimumGapBetweenInstallments() {
return this.minimumGap;
}
public Integer getMaximumGapBetweenInstallments() {
return this.maximumGap;
}
}
@@ -51,7 +51,6 @@ import org.mifosplatform.portfolio.fund.domain.Fund;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.springframework.data.jpa.domain.AbstractPersistable;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductConfigurableAttributes;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
@@ -164,6 +163,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
private LoanProductFloatingRates floatingRates;
@Column(name = "allow_variabe_installments", nullable = false)
private boolean allowVariabeInstallments;
@LazyCollection(LazyCollectionOption.FALSE)
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
private LoanProductVariableInstallmentConfig variableInstallmentConfig;
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) {
@@ -195,6 +201,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
BigDecimal maxDifferentialLendingRate = null;
BigDecimal defaultDifferentialLendingRate = null;
Boolean isFloatingInterestRateCalculationAllowed = null;
Integer minimumGapBetweenInstallments = null;
Integer maximumGapBetweenInstallments = null;
final Boolean isLinkedToFloatingInterestRates = command.booleanObjectValueOfParameterNamed("isLinkedToFloatingInterestRates");
if (isLinkedToFloatingInterestRates != null && isLinkedToFloatingInterestRates) {
interestRateDifferential = command.bigDecimalValueOfParameterNamed("interestRateDifferential");
@@ -211,6 +221,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod);
}
final Boolean isVariableInstallmentsAllowed = command
.booleanObjectValueOfParameterNamed(LoanProductConstants.allowVariableInstallmentsParamName);
if (isVariableInstallmentsAllowed != null && isVariableInstallmentsAllowed) {
minimumGapBetweenInstallments = command.integerValueOfParameterNamed(LoanProductConstants.minimumGapBetweenInstallments);
maximumGapBetweenInstallments = command.integerValueOfParameterNamed(LoanProductConstants.maximumGapBetweenInstallments);
}
final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery");
final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments");
final Integer minNumberOfRepayments = command.integerValueOfParameterNamed("minNumberOfRepayments");
@@ -304,7 +321,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineEmiAmount, installmentAmountInMultiplesOf,
loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate, interestRateDifferential,
minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate,
isFloatingInterestRateCalculationAllowed);
isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments,
maximumGapBetweenInstallments);
}
@@ -534,7 +552,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
final Integer installmentAmountInMultiplesOf, final LoanProductConfigurableAttributes loanProductConfigurableAttributes,
Boolean isLinkedToFloatingInterestRates, FloatingRate floatingRate, BigDecimal interestRateDifferential,
BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate,
Boolean isFloatingInterestRateCalculationAllowed) {
Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed,
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -555,6 +574,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
maxDifferentialLendingRate, defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed);
}
this.allowVariabeInstallments = isVariableInstallmentsAllowed == null ? false : isVariableInstallmentsAllowed;
if (allowVariabeInstallments) {
this.variableInstallmentConfig = new LoanProductVariableInstallmentConfig(this, minimumGapBetweenInstallments,
maximumGapBetweenInstallments);
}
this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery,
repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, graceOnInterestPayment,
@@ -674,8 +700,20 @@ public class LoanProduct extends AbstractPersistable<Long> {
actualChanges.putAll(loanProductFloatingRates().update(command, floatingRate));
this.loanProductRelatedDetail.updateForFloatingInterestRates();
this.loanProductMinMaxConstraints.updateForFloatingInterestRates();
}else{
this.floatingRates = null;
} else {
this.floatingRates = null;
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.allowVariableInstallmentsParamName, this.allowVariabeInstallments)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.allowVariableInstallmentsParamName);
actualChanges.put(LoanProductConstants.allowVariableInstallmentsParamName, newValue);
this.allowVariabeInstallments = newValue;
}
if (this.allowVariabeInstallments) {
actualChanges.putAll(loanProductVariableInstallmentConfig().update(command));
} else {
this.variableInstallmentConfig = null;
}
final String accountingTypeParamName = "accountingRule";
@@ -964,13 +1002,18 @@ public class LoanProduct extends AbstractPersistable<Long> {
return actualChanges;
}
private LoanProductFloatingRates loanProductFloatingRates() {
this.floatingRates = this.floatingRates == null
? new LoanProductFloatingRates(null, this, null, null, null, null, false)
: this.floatingRates;
private LoanProductFloatingRates loanProductFloatingRates() {
this.floatingRates = this.floatingRates == null ? new LoanProductFloatingRates(null, this, null, null, null, null, false)
: this.floatingRates;
return this.floatingRates;
}
public LoanProductVariableInstallmentConfig loanProductVariableInstallmentConfig() {
this.variableInstallmentConfig = this.variableInstallmentConfig == null ? new LoanProductVariableInstallmentConfig(this, null, null)
: this.variableInstallmentConfig;
return this.variableInstallmentConfig;
}
public boolean isAccountingDisabled() {
return AccountingRuleType.NONE.getValue().equals(this.accountingRule);
}
@@ -1268,4 +1311,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
return applicableRates;
}
public boolean allowVariabeInstallments() {
return this.allowVariabeInstallments;
}
}
@@ -0,0 +1,75 @@
/**
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.mifosplatform.portfolio.loanproduct.domain;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import org.mifosplatform.infrastructure.core.api.JsonCommand;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@Table(name = "m_product_loan_variable_installment_config")
public class LoanProductVariableInstallmentConfig extends AbstractPersistable<Long> {
@OneToOne
@JoinColumn(name = "loan_product_id", nullable = false)
private LoanProduct loanProduct;
@Column(name = "minimum_gap")
private Integer minimumGap;
@Column(name = "maximum_gap")
private Integer maximumGap;
protected LoanProductVariableInstallmentConfig() {
}
public LoanProductVariableInstallmentConfig(final LoanProduct loanProduct, final Integer minimumGap, final Integer maximumGap) {
this.loanProduct = loanProduct;
this.minimumGap = minimumGap;
this.maximumGap = maximumGap;
}
public void setLoanProduct(final LoanProduct loanProduct) {
this.loanProduct = loanProduct;
}
public Map<? extends String, ? extends Object> update(JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(3);
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.minimumGapBetweenInstallments, this.minimumGap)) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.minimumGapBetweenInstallments);
actualChanges.put(LoanProductConstants.minimumGapBetweenInstallments, newValue);
this.minimumGap = newValue;
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.maximumGapBetweenInstallments, this.maximumGap)) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.maximumGapBetweenInstallments);
actualChanges.put(LoanProductConstants.maximumGapBetweenInstallments, newValue);
this.maximumGap = newValue;
}
return actualChanges;
}
public Integer getMinimumGap() {
return this.minimumGap;
}
public Integer getMaximumGap() {
return this.maximumGap;
}
}
@@ -87,7 +87,9 @@ public final class LoanProductDataValidator {
LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, LoanProductConstants.principalThresholdForLastInstallmentParamName,
LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, LoanProductConstants.canDefineEmiAmountParamName,
LoanProductConstants.installmentAmountInMultiplesOfParamName,
LoanProductConstants.preClosureInterestCalculationStrategyParamName, LoanProductConstants.allowAttributeOverridesParamName));
LoanProductConstants.preClosureInterestCalculationStrategyParamName, LoanProductConstants.allowAttributeOverridesParamName,
LoanProductConstants.allowVariableInstallmentsParamName, LoanProductConstants.minimumGapBetweenInstallments,
LoanProductConstants.maximumGapBetweenInstallments));
private final FromJsonHelper fromApiJsonHelper;
@@ -290,58 +292,74 @@ public final class LoanProductDataValidator {
}
}
//interest rates
if(this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)
&& this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true){
if(this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)){
baseDataValidator.reset().parameter("interestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"interestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
// interest rates
if (this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)
&& this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true) {
if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
baseDataValidator
.reset()
.parameter("interestRatePerPeriod")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"interestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
}
if(this.fromApiJsonHelper.parameterExists("minInterestRatePerPeriod", element)){
baseDataValidator.reset().parameter("minInterestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"minInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
if (this.fromApiJsonHelper.parameterExists("minInterestRatePerPeriod", element)) {
baseDataValidator
.reset()
.parameter("minInterestRatePerPeriod")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"minInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
}
if(this.fromApiJsonHelper.parameterExists("maxInterestRatePerPeriod", element)){
baseDataValidator.reset().parameter("maxInterestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"maxInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
if (this.fromApiJsonHelper.parameterExists("maxInterestRatePerPeriod", element)) {
baseDataValidator
.reset()
.parameter("maxInterestRatePerPeriod")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"maxInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
}
if(this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)){
baseDataValidator.reset().parameter("interestRateFrequencyType").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"interestRateFrequencyType param is not supported when isLinkedToFloatingInterestRates is true");
if (this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)) {
baseDataValidator
.reset()
.parameter("interestRateFrequencyType")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"interestRateFrequencyType param is not supported when isLinkedToFloatingInterestRates is true");
}
if((interestType == null || interestType != InterestMethod.DECLINING_BALANCE.getValue())
|| (isInterestRecalculationEnabled == null || isInterestRecalculationEnabled == false)){
baseDataValidator.reset().parameter("isLinkedToFloatingInterestRates").failWithCode("supported.only.for.declining.balance.interest.recalculation.enabled",
"Floating interest rates are supported only for declining balance and interest recalculation enabled loan products");
}
final Integer floatingRatesId = this.fromApiJsonHelper.extractIntegerNamed("floatingRatesId", element,
Locale.getDefault());
if ((interestType == null || interestType != InterestMethod.DECLINING_BALANCE.getValue())
|| (isInterestRecalculationEnabled == null || isInterestRecalculationEnabled == false)) {
baseDataValidator
.reset()
.parameter("isLinkedToFloatingInterestRates")
.failWithCode("supported.only.for.declining.balance.interest.recalculation.enabled",
"Floating interest rates are supported only for declining balance and interest recalculation enabled loan products");
}
final Integer floatingRatesId = this.fromApiJsonHelper.extractIntegerNamed("floatingRatesId", element, Locale.getDefault());
baseDataValidator.reset().parameter("floatingRatesId").value(floatingRatesId).notNull();
final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRateDifferential", element);
baseDataValidator.reset().parameter("interestRateDifferential").value(interestRateDifferential).notNull().zeroOrPositiveAmount();
final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRateDifferential",
element);
baseDataValidator.reset().parameter("interestRateDifferential").value(interestRateDifferential).notNull()
.zeroOrPositiveAmount();
final String minDifferentialLendingRateParameterName = "minDifferentialLendingRate";
BigDecimal minDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minDifferentialLendingRateParameterName,
element);
BigDecimal minDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
minDifferentialLendingRateParameterName, element);
baseDataValidator.reset().parameter(minDifferentialLendingRateParameterName).value(minDifferentialLendingRate).notNull()
.zeroOrPositiveAmount();
.zeroOrPositiveAmount();
final String defaultDifferentialLendingRateParameterName = "defaultDifferentialLendingRate";
BigDecimal defaultDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(defaultDifferentialLendingRateParameterName,
element);
baseDataValidator.reset().parameter(defaultDifferentialLendingRateParameterName).value(defaultDifferentialLendingRate).notNull()
.zeroOrPositiveAmount();
BigDecimal defaultDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
defaultDifferentialLendingRateParameterName, element);
baseDataValidator.reset().parameter(defaultDifferentialLendingRateParameterName).value(defaultDifferentialLendingRate)
.notNull().zeroOrPositiveAmount();
final String maxDifferentialLendingRateParameterName = "maxDifferentialLendingRate";
BigDecimal maxDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxDifferentialLendingRateParameterName,
element);
BigDecimal maxDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
maxDifferentialLendingRateParameterName, element);
baseDataValidator.reset().parameter(maxDifferentialLendingRateParameterName).value(maxDifferentialLendingRate).notNull()
.zeroOrPositiveAmount();
.zeroOrPositiveAmount();
if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
@@ -349,57 +367,76 @@ public final class LoanProductDataValidator {
.notLessThanMin(minDifferentialLendingRate);
}
}
if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate)
.notLessThanMin(minDifferentialLendingRate);
}
}
if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate)
.notLessThanMin(defaultDifferentialLendingRate);
}
}
final Boolean isFloatingInterestRateCalculationAllowed = this.fromApiJsonHelper.extractBooleanNamed(
"isFloatingInterestRateCalculationAllowed", element);
baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed")
.value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false);
"isFloatingInterestRateCalculationAllowed", element);
baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed").value(isFloatingInterestRateCalculationAllowed)
.notNull().isOneOfTheseValues(true, false);
} else {
if(this.fromApiJsonHelper.parameterExists("floatingRatesId", element)){
baseDataValidator.reset().parameter("floatingRatesId").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"floatingRatesId param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("floatingRatesId", element)) {
baseDataValidator
.reset()
.parameter("floatingRatesId")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"floatingRatesId param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)){
baseDataValidator.reset().parameter("interestRateDifferential").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"interestRateDifferential param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)) {
baseDataValidator
.reset()
.parameter("interestRateDifferential")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"interestRateDifferential param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("minDifferentialLendingRate", element)){
baseDataValidator.reset().parameter("minDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"minDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("minDifferentialLendingRate", element)) {
baseDataValidator
.reset()
.parameter("minDifferentialLendingRate")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"minDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("defaultDifferentialLendingRate", element)){
baseDataValidator.reset().parameter("defaultDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"defaultDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("defaultDifferentialLendingRate", element)) {
baseDataValidator
.reset()
.parameter("defaultDifferentialLendingRate")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"defaultDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("maxDifferentialLendingRate", element)){
baseDataValidator.reset().parameter("maxDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"maxDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("maxDifferentialLendingRate", element)) {
baseDataValidator
.reset()
.parameter("maxDifferentialLendingRate")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"maxDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)){
baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)) {
baseDataValidator
.reset()
.parameter("isFloatingInterestRateCalculationAllowed")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod",
element);
baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount();
final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod";
@@ -442,7 +479,6 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter("interestRateFrequencyType").value(interestRateFrequencyType).notNull().inMinMaxRange(0, 3);
}
// Guarantee Funds
Boolean holdGuaranteeFunds = false;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
@@ -563,9 +599,58 @@ public final class LoanProductDataValidator {
validateLoanConfigurableAttributes(baseDataValidator, element);
validateVariableInstallmentSettings(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateVariableInstallmentSettings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowVariableInstallmentsParamName, element)
&& this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.allowVariableInstallmentsParamName, element)) {
Long minimumGapBetweenInstallments = null;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGapBetweenInstallments, element)) {
minimumGapBetweenInstallments = this.fromApiJsonHelper.extractLongNamed(LoanProductConstants.minimumGapBetweenInstallments,
element);
baseDataValidator.reset().parameter(LoanProductConstants.minimumGapBetweenInstallments)
.value(minimumGapBetweenInstallments).notNull();
} else {
baseDataValidator
.reset()
.parameter(LoanProductConstants.minimumGapBetweenInstallments)
.failWithCode("is.mandatory.when.allowVariableInstallments.is.true",
"minimumGap param is mandatory when allowVariableInstallments is true");
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.maximumGapBetweenInstallments, element)) {
final Long maximumGapBetweenInstallments = this.fromApiJsonHelper.extractLongNamed(
LoanProductConstants.maximumGapBetweenInstallments, element);
baseDataValidator.reset().parameter(LoanProductConstants.minimumGapBetweenInstallments)
.value(maximumGapBetweenInstallments).notNull();
baseDataValidator.reset().parameter(LoanProductConstants.maximumGapBetweenInstallments)
.value(maximumGapBetweenInstallments).notNull().longGreaterThanNumber(minimumGapBetweenInstallments);
}
} else {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.minimumGapBetweenInstallments, element)) {
baseDataValidator
.reset()
.parameter(LoanProductConstants.minimumGapBetweenInstallments)
.failWithCode("not.supported.when.allowVariableInstallments.is.false",
"minimumGap param is not supported when allowVariableInstallments is not supplied or false");
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.maximumGapBetweenInstallments, element)) {
baseDataValidator
.reset()
.parameter(LoanProductConstants.maximumGapBetweenInstallments)
.failWithCode("not.supported.when.allowVariableInstallments.is.false",
"maximumGap param is not supported when allowVariableInstallments is not supplied or false");
}
}
}
private void validateLoanConfigurableAttributes(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowAttributeOverridesParamName, element)) {
@@ -752,13 +837,13 @@ public final class LoanProductDataValidator {
LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, element)) {
final Integer recurrenceInterval = this.fromApiJsonHelper.extractIntegerNamed(
LoanProductConstants.recalculationCompoundingFrequencyIntervalParameterName, element, Locale.getDefault());
Integer repaymentEvery = null;
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);
}
@@ -989,82 +1074,99 @@ public final class LoanProductDataValidator {
}
}
//interest rates
// interest rates
boolean isLinkedToFloatingInterestRates = loanProduct.isLinkedToFloatingInterestRate();
if(this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)){
isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element);
if (this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)) {
isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element);
}
if(isLinkedToFloatingInterestRates){
if(this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)){
baseDataValidator.reset().parameter("interestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"interestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
if (isLinkedToFloatingInterestRates) {
if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
baseDataValidator
.reset()
.parameter("interestRatePerPeriod")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"interestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
}
if(this.fromApiJsonHelper.parameterExists("minInterestRatePerPeriod", element)){
baseDataValidator.reset().parameter("minInterestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"minInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
if (this.fromApiJsonHelper.parameterExists("minInterestRatePerPeriod", element)) {
baseDataValidator
.reset()
.parameter("minInterestRatePerPeriod")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"minInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
}
if(this.fromApiJsonHelper.parameterExists("maxInterestRatePerPeriod", element)){
baseDataValidator.reset().parameter("maxInterestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"maxInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
if (this.fromApiJsonHelper.parameterExists("maxInterestRatePerPeriod", element)) {
baseDataValidator
.reset()
.parameter("maxInterestRatePerPeriod")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"maxInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true");
}
if(this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)){
baseDataValidator.reset().parameter("interestRateFrequencyType").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"interestRateFrequencyType param is not supported when isLinkedToFloatingInterestRates is true");
if (this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)) {
baseDataValidator
.reset()
.parameter("interestRateFrequencyType")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true",
"interestRateFrequencyType param is not supported when isLinkedToFloatingInterestRates is true");
}
Integer interestType = this.fromApiJsonHelper.parameterExists("interestType", element)
? this.fromApiJsonHelper.extractIntegerNamed("interestType", element, Locale.getDefault())
: loanProduct.getLoanProductRelatedDetail().getInterestMethod().getValue();
if((interestType == null || interestType != InterestMethod.DECLINING_BALANCE.getValue())
|| (isInterestRecalculationEnabled == null || isInterestRecalculationEnabled == false)){
baseDataValidator.reset().parameter("isLinkedToFloatingInterestRates").failWithCode("supported.only.for.declining.balance.interest.recalculation.enabled",
"Floating interest rates are supported only for declining balance and interest recalculation enabled loan products");
}
Long floatingRatesId = loanProduct.getFloatingRates() == null? null: loanProduct.getFloatingRates().getFloatingRate().getId();
if(this.fromApiJsonHelper.parameterExists("floatingRatesId", element)){
Integer interestType = this.fromApiJsonHelper.parameterExists("interestType", element) ? this.fromApiJsonHelper
.extractIntegerNamed("interestType", element, Locale.getDefault()) : loanProduct.getLoanProductRelatedDetail()
.getInterestMethod().getValue();
if ((interestType == null || interestType != InterestMethod.DECLINING_BALANCE.getValue())
|| (isInterestRecalculationEnabled == null || isInterestRecalculationEnabled == false)) {
baseDataValidator
.reset()
.parameter("isLinkedToFloatingInterestRates")
.failWithCode("supported.only.for.declining.balance.interest.recalculation.enabled",
"Floating interest rates are supported only for declining balance and interest recalculation enabled loan products");
}
Long floatingRatesId = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates().getFloatingRate().getId();
if (this.fromApiJsonHelper.parameterExists("floatingRatesId", element)) {
floatingRatesId = this.fromApiJsonHelper.extractLongNamed("floatingRatesId", element);
}
}
baseDataValidator.reset().parameter("floatingRatesId").value(floatingRatesId).notNull();
BigDecimal interestRateDifferential = loanProduct.getFloatingRates() == null? null: loanProduct.getFloatingRates().getInterestRateDifferential();
if(this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)){
BigDecimal interestRateDifferential = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates()
.getInterestRateDifferential();
if (this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)) {
interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRateDifferential", element);
}
baseDataValidator.reset().parameter("interestRateDifferential").value(interestRateDifferential).notNull().zeroOrPositiveAmount();
}
baseDataValidator.reset().parameter("interestRateDifferential").value(interestRateDifferential).notNull()
.zeroOrPositiveAmount();
final String minDifferentialLendingRateParameterName = "minDifferentialLendingRate";
BigDecimal minDifferentialLendingRate = loanProduct.getFloatingRates() == null
? null:loanProduct.getFloatingRates().getMinDifferentialLendingRate();
BigDecimal minDifferentialLendingRate = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates()
.getMinDifferentialLendingRate();
if (this.fromApiJsonHelper.parameterExists(minDifferentialLendingRateParameterName, element)) {
minDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minDifferentialLendingRateParameterName,
element);
minDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
minDifferentialLendingRateParameterName, element);
}
baseDataValidator.reset().parameter(minDifferentialLendingRateParameterName).value(minDifferentialLendingRate).notNull()
.zeroOrPositiveAmount();
.zeroOrPositiveAmount();
final String defaultDifferentialLendingRateParameterName = "defaultDifferentialLendingRate";
BigDecimal defaultDifferentialLendingRate = loanProduct.getFloatingRates() == null
? null:loanProduct.getFloatingRates().getDefaultDifferentialLendingRate();
BigDecimal defaultDifferentialLendingRate = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates()
.getDefaultDifferentialLendingRate();
if (this.fromApiJsonHelper.parameterExists(defaultDifferentialLendingRateParameterName, element)) {
defaultDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(defaultDifferentialLendingRateParameterName,
element);
defaultDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
defaultDifferentialLendingRateParameterName, element);
}
baseDataValidator.reset().parameter(defaultDifferentialLendingRateParameterName).value(defaultDifferentialLendingRate).notNull()
.zeroOrPositiveAmount();
baseDataValidator.reset().parameter(defaultDifferentialLendingRateParameterName).value(defaultDifferentialLendingRate)
.notNull().zeroOrPositiveAmount();
final String maxDifferentialLendingRateParameterName = "maxDifferentialLendingRate";
BigDecimal maxDifferentialLendingRate = loanProduct.getFloatingRates() == null
? null:loanProduct.getFloatingRates().getMaxDifferentialLendingRate();
BigDecimal maxDifferentialLendingRate = loanProduct.getFloatingRates() == null ? null : loanProduct.getFloatingRates()
.getMaxDifferentialLendingRate();
if (this.fromApiJsonHelper.parameterExists(maxDifferentialLendingRateParameterName, element)) {
maxDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxDifferentialLendingRateParameterName,
element);
maxDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(
maxDifferentialLendingRateParameterName, element);
}
baseDataValidator.reset().parameter(maxDifferentialLendingRateParameterName).value(maxDifferentialLendingRate).notNull()
.zeroOrPositiveAmount();
.zeroOrPositiveAmount();
if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
@@ -1072,60 +1174,78 @@ public final class LoanProductDataValidator {
.notLessThanMin(minDifferentialLendingRate);
}
}
if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate)
.notLessThanMin(minDifferentialLendingRate);
}
}
if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) {
baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate)
.notLessThanMin(defaultDifferentialLendingRate);
}
}
Boolean isFloatingInterestRateCalculationAllowed = loanProduct.getFloatingRates() == null ? null
: loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed();
if(this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)){
Boolean isFloatingInterestRateCalculationAllowed = loanProduct.getFloatingRates() == null ? null : loanProduct
.getFloatingRates().isFloatingInterestRateCalculationAllowed();
if (this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)) {
isFloatingInterestRateCalculationAllowed = this.fromApiJsonHelper.extractBooleanNamed(
"isFloatingInterestRateCalculationAllowed", element);
"isFloatingInterestRateCalculationAllowed", element);
}
baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed")
.value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false);
baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed").value(isFloatingInterestRateCalculationAllowed)
.notNull().isOneOfTheseValues(true, false);
} else {
if(this.fromApiJsonHelper.parameterExists("floatingRatesId", element)){
baseDataValidator.reset().parameter("floatingRatesId").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"floatingRatesId param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("floatingRatesId", element)) {
baseDataValidator
.reset()
.parameter("floatingRatesId")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"floatingRatesId param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)){
baseDataValidator.reset().parameter("interestRateDifferential").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"interestRateDifferential param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)) {
baseDataValidator
.reset()
.parameter("interestRateDifferential")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"interestRateDifferential param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("minDifferentialLendingRate", element)){
baseDataValidator.reset().parameter("minDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"minDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("minDifferentialLendingRate", element)) {
baseDataValidator
.reset()
.parameter("minDifferentialLendingRate")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"minDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("defaultDifferentialLendingRate", element)){
baseDataValidator.reset().parameter("defaultDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"defaultDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("defaultDifferentialLendingRate", element)) {
baseDataValidator
.reset()
.parameter("defaultDifferentialLendingRate")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"defaultDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("maxDifferentialLendingRate", element)){
baseDataValidator.reset().parameter("maxDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"maxDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("maxDifferentialLendingRate", element)) {
baseDataValidator
.reset()
.parameter("maxDifferentialLendingRate")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"maxDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
if(this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)){
baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
if (this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)) {
baseDataValidator
.reset()
.parameter("isFloatingInterestRateCalculationAllowed")
.failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false",
"isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod";
BigDecimal minInterestRatePerPeriod = loanProduct.getMinNominalInterestRatePerPeriod();
if (this.fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) {
@@ -1133,7 +1253,7 @@ public final class LoanProductDataValidator {
element);
}
baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull()
.zeroOrPositiveAmount();
.zeroOrPositiveAmount();
final String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod";
BigDecimal maxInterestRatePerPeriod = loanProduct.getMaxNominalInterestRatePerPeriod();
@@ -1142,12 +1262,11 @@ public final class LoanProductDataValidator {
element);
}
baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
.zeroOrPositiveAmount();
.zeroOrPositiveAmount();
BigDecimal interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) {
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod",
element);
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
}
baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount();
@@ -1275,6 +1394,7 @@ public final class LoanProductDataValidator {
// validateLoanConfigurableAttributes(baseDataValidator,element);
validateVariableInstallmentSettings(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1556,11 +1676,8 @@ public final class LoanProductDataValidator {
private void validateNominalInterestRatePerPeriodMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct,
final DataValidatorBuilder baseDataValidator) {
if((this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)
&& this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true)
|| loanProduct.isLinkedToFloatingInterestRate()){
return;
}
if ((this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element) && this.fromApiJsonHelper
.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true) || loanProduct.isLinkedToFloatingInterestRate()) { return; }
boolean iRPUpdated = false;
boolean minIRPUpdated = false;
boolean maxIRPUpdated = false;
@@ -514,7 +514,23 @@ public class LoanEnumerations {
case INTEREST_RATE:
optionData = new EnumOptionData(LoanTermVariationType.INTEREST_RATE.getValue().longValue(),
LoanTermVariationType.INTEREST_RATE.getCode(), "interestRate");
break;
break;
case DELETE_INSTALLMENT:
optionData = new EnumOptionData(LoanTermVariationType.DELETE_INSTALLMENT.getValue().longValue(),
LoanTermVariationType.DELETE_INSTALLMENT.getCode(), "deleteInstallment");
break;
case DUE_DATE:
optionData = new EnumOptionData(LoanTermVariationType.DUE_DATE.getValue().longValue(),
LoanTermVariationType.DUE_DATE.getCode(), "dueDate");
break;
case INSERT_INSTALLMENT:
optionData = new EnumOptionData(LoanTermVariationType.INSERT_INSTALLMENT.getValue().longValue(),
LoanTermVariationType.DUE_DATE.getCode(), "insertInstallment");
break;
case PRINCIPAL_AMOUNT:
optionData = new EnumOptionData(LoanTermVariationType.PRINCIPAL_AMOUNT.getValue().longValue(),
LoanTermVariationType.PRINCIPAL_AMOUNT.getCode(), "principalAmount");
break;
default:
optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(),
LoanTermVariationType.INVALID.getCode(), "Invalid");
@@ -200,7 +200,10 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lfr.min_differential_lending_rate as minDifferentialLendingRate, "
+ "lfr.default_differential_lending_rate as defaultDifferentialLendingRate, "
+ "lfr.max_differential_lending_rate as maxDifferentialLendingRate, "
+ "lfr.is_floating_interest_rate_calculation_allowed as isFloatingInterestRateCalculationAllowed "
+ "lfr.is_floating_interest_rate_calculation_allowed as isFloatingInterestRateCalculationAllowed, "
+ "lp.allow_variabe_installments as isVariableIntallmentsAllowed, "
+ "lvi.minimum_gap as minimumGap, "
+ "lvi.maximum_gap as maximumGap "
+ " from m_product_loan lp "
+ " left join m_fund f on f.id = lp.fund_id "
+ " left join m_product_loan_recalculation_details lpr on lpr.product_id=lp.id "
@@ -209,7 +212,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ " left join m_product_loan_configurable_attributes lca on lca.loan_product_id = lp.id "
+ " left join m_product_loan_floating_rates as lfr on lfr.loan_product_id = lp.id "
+ " left join m_floating_rates as fr on lfr.floating_rates_id = fr.id "
+ " left join m_product_loan_variable_installment_config as lvi on lvi.loan_product_id = lp.id "
+ " join m_currency curr on curr.code = lp.currency_code";
}
@Override
@@ -268,7 +273,11 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final BigDecimal defaultDifferentialLendingRate = rs.getBigDecimal("defaultDifferentialLendingRate");
final BigDecimal maxDifferentialLendingRate = rs.getBigDecimal("maxDifferentialLendingRate");
final boolean isFloatingInterestRateCalculationAllowed = rs.getBoolean("isFloatingInterestRateCalculationAllowed");
final boolean isVariableIntallmentsAllowed = rs.getBoolean("isVariableIntallmentsAllowed");
final Integer minimumGap = rs.getInt("minimumGap");
final Integer maximumGap = rs.getInt("maximumGap");
final int repaymentFrequencyTypeId = JdbcSupport.getInteger(rs, "repaymentPeriodFrequency");
final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeId);
@@ -341,8 +350,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final Integer compoundingFrequencyEnumValue = JdbcSupport.getInteger(rs, "compoundingFrequencyTypeEnum");
EnumOptionData compoundingFrequencyType = null;
if (compoundingFrequencyEnumValue != null) {
compoundingFrequencyType = LoanEnumerations
.interestRecalculationFrequencyType(compoundingFrequencyEnumValue);
compoundingFrequencyType = LoanEnumerations.interestRecalculationFrequencyType(compoundingFrequencyEnumValue);
}
final Integer compoundingInterval = JdbcSupport.getInteger(rs, "compoundingInterval");
final LocalDate compoundingFrequencyDate = JdbcSupport.getLocalDate(rs, "compoundingFrequencyDate");
@@ -397,8 +405,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData,
principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount,
installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId,
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed);
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
maximumGap);
}
}
@@ -0,0 +1,26 @@
ALTER TABLE `m_product_loan`
ADD COLUMN `allow_variabe_installments` BIT(1) NOT NULL DEFAULT 0 AFTER `is_linked_to_floating_interest_rates` ;
CREATE TABLE `m_product_loan_variable_installment_config` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`loan_product_id` BIGINT(20) NOT NULL,
`minimum_gap` INT(4) NOT NULL,
`maximum_gap` INT(4) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `FK_mappings_m_variable_product_loan_id` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`)
);
INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES ('portfolio', 'CREATESCHEDULEEXCEPTIONS_LOAN', 'LOAN', 'CREATESCHEDULEEXCEPTIONS', 0), ('portfolio', 'CREATESCHEDULEEXCEPTIONS_LOAN_CHECKER', 'LOAN', 'CREATESCHEDULEEXCEPTIONS_CHECKER', 0), ('portfolio', 'DELETESCHEDULEEXCEPTIONS_LOAN', 'LOAN', 'DELETESCHEDULEEXCEPTIONS', 0),('portfolio', 'DELETESCHEDULEEXCEPTIONS_LOAN_CHECKER', 'LOAN', 'DELETESCHEDULEEXCEPTIONS_CHECKER', 0);
ALTER TABLE `m_loan_term_variations`
ALTER `applicable_from` DROP DEFAULT,
ALTER `term_value` DROP DEFAULT;
ALTER TABLE `m_loan_term_variations`
CHANGE COLUMN `applicable_from` `applicable_date` DATE NOT NULL AFTER `term_type`,
CHANGE COLUMN `term_value` `decimal_value` DECIMAL(19,6) NULL AFTER `applicable_date`,
ADD COLUMN `date_value` DATE NULL AFTER `decimal_value`,
ADD COLUMN `is_specific_to_installment` TINYINT NOT NULL DEFAULT '0' AFTER `date_value`,
ADD COLUMN `applied_on_loan_status` SMALLINT(5) NOT NULL AFTER `is_specific_to_installment`;
UPDATE `m_loan_term_variations` SET `applied_on_loan_status`=300;