Merge pull request #1033 from lingalamadhukar/MIFOSX-1420
MIFOSX-1419,MIFOSX-1420 Interest recalculation crud operations
This commit is contained in:
+269
-6
@@ -6676,6 +6676,21 @@ An enumeration that indicates the type of transaction processing strategy to be
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>recalculationFrequencyType</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Frequency at which Outstanding Principal is recalculated. This field is mandatory when interest interest recalculation is enabled for product.
|
||||
<br>Available calculation types:
|
||||
<ol>
|
||||
<li>1 = Same as repayment period</li>
|
||||
<li>2 = Daily</li>
|
||||
<li>3 = Weekly</li>
|
||||
<li>4 = Fortnightly</li>
|
||||
<li>5 = Monthly</li>
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6826,6 +6841,29 @@ GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1&
|
||||
8
|
||||
]
|
||||
},
|
||||
"daysInMonthType": {
|
||||
"id": 30,
|
||||
"code": "DaysInMonthType.days360",
|
||||
"value": "30 Days"
|
||||
},
|
||||
"daysInYearType": {
|
||||
"id": 360,
|
||||
"code": "DaysInYearType.days360",
|
||||
"value": "360 Days"
|
||||
},
|
||||
"isInterestRecalculationEnabled": true,
|
||||
"interestRecalculationData": {
|
||||
"interestRecalculationCompoundingType": {
|
||||
"id": 2,
|
||||
"code": "interestRecalculationCompoundingMethod.fee",
|
||||
"value": "Fee"
|
||||
},
|
||||
"rescheduleStrategyType": {
|
||||
"id": 2,
|
||||
"code": "loanRescheduleStrategyMethod.reduce.number.of.installments",
|
||||
"value": "Reduce number of installments"
|
||||
}
|
||||
}
|
||||
"charges": [],
|
||||
"productOptions": [
|
||||
{
|
||||
@@ -7015,7 +7053,32 @@ GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1&
|
||||
"fieldOfficerId":0,
|
||||
"currency":{"code":"USD","name":"US Dollar","decimalPlaces":2,"displaySymbol":"$","nameCode":"currency.USD","displayLabel":"US Dollar ($)"}
|
||||
}
|
||||
]
|
||||
],
|
||||
"interestRecalculationFrequencyTypeOptions": [{
|
||||
"id": 1,
|
||||
"code": "interestRecalculationFrequencyType.same.as.repayment.period",
|
||||
"value": "Same as repayment period"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"code": "interestRecalculationFrequencyType.daily",
|
||||
"value": "Daily"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"code": "interestRecalculationFrequencyType.weekly",
|
||||
"value": "Weekly"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"code": "interestRecalculationFrequencyType.fortnightly",
|
||||
"value": "Fortnightly"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"code": "interestRecalculationFrequencyType.monthly",
|
||||
"value": "Monthly"
|
||||
}],
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
@@ -7753,6 +7816,15 @@ Request Body:
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Additional Mandatory Fields if interest recalculation is enabled for product</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>recalculationFrequencyType</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Optional Fields</div></td>
|
||||
@@ -7793,6 +7865,7 @@ Request Body:
|
||||
"submittedOnDate": "10 Jun 2013",
|
||||
"linkAccountId" : "1",
|
||||
"fixedEmiAmount":1100,
|
||||
"recalculationFrequencyType":"1",
|
||||
"maxOutstandingLoanBalance":"35000",
|
||||
"disbursementData":[{"expectedDisbursementDate":"01 November 2013",
|
||||
"principal":22000,"approvedPrincipal":22000}]
|
||||
@@ -10171,8 +10244,39 @@ An enumeration that indicates the type of transaction processing strategy to be
|
||||
product and the type of the accounting rule to be used
|
||||
<span>Example Values:</span>1=NONE, 2=CASH_BASED, 3=ACCRUAL_PERIODIC, 4=ACCRUAL_UPFRONT</td>
|
||||
</tr>
|
||||
</td>
|
||||
<tr class=alt>
|
||||
<td>isInterestRecalculationEnabled</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>It is a flag, Used to denote whether interest recalculation is enabled or disabled for the particular product</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>daysInYearType</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Specifies the number of days in a year. <br>
|
||||
<span>Example Values:</span>1=ACTUAL(Actual number of days in year), 360=360 DAYS, 364=364 DAYS(52 WEEKS), 365=365 DAYS</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>daysInMonthType</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Specifies the number of days in a month. <br>
|
||||
<span>Example Values:</span>1=ACTUAL(Actual number of days in month), 30=30 DAYS</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>interestRecalculationCompoundingMethod</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Specifies which amount portion should be added to principal for interest recalculation. <br>
|
||||
<span>Example Values:</span>0=NONE(Only on principal), 1=INTEREST(Principal+Interest), 2=FEE(Principal+Fee), 3=FEE And INTEREST (Principal+Fee+Interest)</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>rescheduleStrategyMethod</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Specifies what action should perform on loan repayment schedule for advance payments. <br>
|
||||
<span>Example Values:</span>1=Reschedule next repayments, 2=Reduce number of installments, 3=Reduce EMI amount</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -10260,6 +10364,31 @@ GET https://DomainName/api/v1/loanproducts
|
||||
"principalVariationsForBorrowerCycle": [],
|
||||
"interestRateVariationsForBorrowerCycle": [],
|
||||
"numberOfRepaymentVariationsForBorrowerCycle": [],
|
||||
"daysInMonthType": {
|
||||
"id": 30,
|
||||
"code": "DaysInMonthType.days360",
|
||||
"value": "30 Days"
|
||||
},
|
||||
"daysInYearType": {
|
||||
"id": 360,
|
||||
"code": "DaysInYearType.days360",
|
||||
"value": "360 Days"
|
||||
},
|
||||
"isInterestRecalculationEnabled": true,
|
||||
"interestRecalculationData": {
|
||||
"id": 3,
|
||||
"productId": 1,
|
||||
"interestRecalculationCompoundingType": {
|
||||
"id": 2,
|
||||
"code": "interestRecalculationCompoundingMethod.fee",
|
||||
"value": "Fee"
|
||||
},
|
||||
"rescheduleStrategyType": {
|
||||
"id": 2,
|
||||
"code": "loanRescheduleStrategyMethod.reduce.number.of.installments",
|
||||
"value": "Reduce number of installments"
|
||||
}
|
||||
},
|
||||
"accountingRule": {
|
||||
"id": 2,
|
||||
"code": "accountingRuleType.cash",
|
||||
@@ -10338,6 +10467,29 @@ GET https://DomainName/api/v1/loanproducts/template
|
||||
"code": "accountingRuleType.none",
|
||||
"value": "NONE"
|
||||
},
|
||||
"daysInMonthType": {
|
||||
"id": 1,
|
||||
"code": "DaysInMonthType.actual",
|
||||
"value": "Actual"
|
||||
},
|
||||
"daysInYearType": {
|
||||
"id": 1,
|
||||
"code": "DaysInYearType.actual",
|
||||
"value": "Actual"
|
||||
},
|
||||
"isInterestRecalculationEnabled": false,
|
||||
"interestRecalculationData": {
|
||||
"interestRecalculationCompoundingType": {
|
||||
"id": 0,
|
||||
"code": "interestRecalculationCompoundingMethod.none",
|
||||
"value": "None"
|
||||
},
|
||||
"rescheduleStrategyType": {
|
||||
"id": 3,
|
||||
"code": "loanRescheduleStrategyMethod.reduce.emi.amount",
|
||||
"value": "Reduce EMI amount"
|
||||
}
|
||||
},
|
||||
"paymentTypeOptions": [
|
||||
{
|
||||
"id": 10,
|
||||
@@ -11018,7 +11170,72 @@ GET https://DomainName/api/v1/loanproducts/template
|
||||
"code": "LoanProductValueConditionType.greterthan",
|
||||
"value": "greter than"
|
||||
}
|
||||
]
|
||||
],
|
||||
"daysInMonthTypeOptions": [{
|
||||
"id": 1,
|
||||
"code": "DaysInMonthType.actual",
|
||||
"value": "Actual"
|
||||
},
|
||||
{
|
||||
"id": 30,
|
||||
"code": "DaysInMonthType.days360",
|
||||
"value": "30 Days"
|
||||
}],
|
||||
"daysInYearTypeOptions": [{
|
||||
"id": 1,
|
||||
"code": "DaysInYearType.actual",
|
||||
"value": "Actual"
|
||||
},
|
||||
{
|
||||
"id": 360,
|
||||
"code": "DaysInYearType.days360",
|
||||
"value": "360 Days"
|
||||
},
|
||||
{
|
||||
"id": 364,
|
||||
"code": "DaysInYearType.days364",
|
||||
"value": "364 Days"
|
||||
},
|
||||
{
|
||||
"id": 365,
|
||||
"code": "DaysInYearType.days365",
|
||||
"value": "365 Days"
|
||||
}],
|
||||
"interestRecalculationCompoundingTypeOptions": [{
|
||||
"id": 0,
|
||||
"code": "interestRecalculationCompoundingMethod.none",
|
||||
"value": "None"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"code": "interestRecalculationCompoundingMethod.fee",
|
||||
"value": "Fee"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"code": "interestRecalculationCompoundingMethod.interest",
|
||||
"value": "Interest"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"code": "interestRecalculationCompoundingMethod.interest.and.fee",
|
||||
"value": "Fee and Interest"
|
||||
}],
|
||||
"rescheduleStrategyTypeOptions": [{
|
||||
"id": 3,
|
||||
"code": "loanRescheduleStrategyMethod.reduce.emi.amount",
|
||||
"value": "Reduce EMI amount"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"code": "loanRescheduleStrategyMethod.reduce.number.of.installments",
|
||||
"value": "Reduce number of installments"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"code": "loanRescheduleStrategyMethod.reschedule.next.repayments",
|
||||
"value": "Reschedule next repayments"
|
||||
}]
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
@@ -11264,8 +11481,10 @@ GET https://DomainName/api/v1/loanproducts/{productId}
|
||||
numberOfRepayments, repaymentEvery, repaymentFrequencyType,
|
||||
interestRatePerPeriod, interestRateFrequencyType, amortizationType, interestType,
|
||||
interestCalculationPeriodType, transactionProcessingStrategyId,
|
||||
accountingRule</td>
|
||||
accountingRule, isInterestRecalculationEnabled, daysInYearType, daysInMonthType</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Optional Fields</div></td>
|
||||
</tr>
|
||||
@@ -11278,7 +11497,8 @@ GET https://DomainName/api/v1/loanproducts/{productId}
|
||||
outstandingLoanBalance,overdueDaysForNPA
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Additional Mandatory Fields for Cash(2) based accounting</div></td>
|
||||
</tr>
|
||||
@@ -11287,6 +11507,8 @@ GET https://DomainName/api/v1/loanproducts/{productId}
|
||||
incomeFromFeeAccountId, incomeFromPenaltyAccountId,
|
||||
writeOffAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Additional Mandatory Fields for periodic (3) and upfront (4)accrual accounting</div></td>
|
||||
</tr>
|
||||
@@ -11297,6 +11519,14 @@ GET https://DomainName/api/v1/loanproducts/{productId}
|
||||
receivablePenaltyAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Additional Mandatory Fields if interest recalculation is enabled(true)</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>interestRecalculationCompoundingMethod,rescheduleStrategyMethod</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
@@ -11356,7 +11586,10 @@ Request Body:
|
||||
"incomeFromFeeAccountId":8,
|
||||
"incomeFromPenaltyAccountId":9,
|
||||
"writeOffAccountId":10,
|
||||
"overpaymentLiabilityAccountId":11
|
||||
"overpaymentLiabilityAccountId":11,
|
||||
"daysInMonthType":1,
|
||||
"daysInYearType":1,
|
||||
"isInterestRecalculationEnabled":"false"
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
@@ -11385,6 +11618,11 @@ Request Body:
|
||||
"amortizationType": 1,
|
||||
"interestType": 0,
|
||||
"interestCalculationPeriodType": 1,
|
||||
"daysInMonthType":1,
|
||||
"daysInYearType":1,
|
||||
"isInterestRecalculationEnabled":"true",
|
||||
"interestRecalculationCompoundingMethod":"0",
|
||||
"rescheduleStrategyMethod":"3",
|
||||
"accountingRule":"2",
|
||||
"fundSourceAccountId":"4",
|
||||
"loanPortfolioAccountId":"8",
|
||||
@@ -11445,6 +11683,9 @@ Request Body:
|
||||
"amortizationType": 1,
|
||||
"interestType": 0,
|
||||
"interestCalculationPeriodType": 1,
|
||||
"daysInMonthType":1,
|
||||
"daysInYearType":1,
|
||||
"isInterestRecalculationEnabled":"false",
|
||||
"accountingRule":"3",
|
||||
"fundSourceAccountId":"4",
|
||||
"loanPortfolioAccountId":"8",
|
||||
@@ -11493,6 +11734,28 @@ Request Body:
|
||||
"principal": 70000,
|
||||
"locale": "en_GB"
|
||||
}
|
||||
}
|
||||
</code>
|
||||
<code class="method-request">
|
||||
PUT loanproducts/3
|
||||
Content-Type: application/json
|
||||
Request Body:
|
||||
{
|
||||
"locale": "en",
|
||||
"isInterestRecalculationEnabled":"true",
|
||||
"interestRecalculationCompoundingMethod":"0",
|
||||
"rescheduleStrategyMethod":"3",
|
||||
}
|
||||
</code>
|
||||
<code class="method-response">
|
||||
{
|
||||
"resourceId": 1,
|
||||
"changes": {
|
||||
"locale": "en",
|
||||
"isInterestRecalculationEnabled":"true",
|
||||
"interestRecalculationCompoundingMethod":"0",
|
||||
"rescheduleStrategyMethod":"3",
|
||||
}
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
ALTER TABLE `m_product_loan`
|
||||
ADD COLUMN `days_in_month_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `overdue_days_for_npa`,
|
||||
ADD COLUMN `days_in_year_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `days_in_month_enum`,
|
||||
ADD COLUMN `interest_recalculation_enabled` TINYINT NOT NULL DEFAULT '0' AFTER `days_in_year_enum`;
|
||||
|
||||
ALTER TABLE `m_loan`
|
||||
ADD COLUMN `days_in_month_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `accrued_till`,
|
||||
ADD COLUMN `days_in_year_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `days_in_month_enum`,
|
||||
ADD COLUMN `interest_recalculation_enabled` TINYINT NOT NULL DEFAULT '0' AFTER `days_in_year_enum`;
|
||||
|
||||
CREATE TABLE `m_product_loan_recalculation_details` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`product_id` BIGINT(20) NOT NULL,
|
||||
`compound_type_enum` SMALLINT(5) NOT NULL,
|
||||
`reschedule_strategy_enum` SMALLINT(5) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `FK_m_product_loan_m_product_loan_recalculation_details` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`)
|
||||
)
|
||||
COLLATE='latin1_swedish_ci'
|
||||
ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE `m_loan_recalculation_details` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`loan_id` BIGINT(20) NOT NULL,
|
||||
`compound_type_enum` SMALLINT(5) NOT NULL,
|
||||
`reschedule_strategy_enum` SMALLINT(5) NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
CONSTRAINT `FK_m_loan_m_loan_recalculation_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`)
|
||||
)
|
||||
COLLATE='latin1_swedish_ci'
|
||||
ENGINE=InnoDB;
|
||||
+2
@@ -37,6 +37,7 @@ public class LoanApplicationTestBuilder {
|
||||
private String loanType = "individual";
|
||||
private String fixedEmiAmount = "10000";
|
||||
private String maxOutstandingLoanBalance = "36000";
|
||||
private String recalculationFrequencyType = "1";
|
||||
private String graceOnPrincipalPayment = null;
|
||||
private List<HashMap> disbursementData = null;
|
||||
private List<HashMap> charges = new ArrayList<HashMap>();
|
||||
@@ -66,6 +67,7 @@ public class LoanApplicationTestBuilder {
|
||||
map.put("expectedDisbursementDate", this.expectedDisbursmentDate);
|
||||
map.put("submittedOnDate", this.submittedOnDate);
|
||||
map.put("loanType", this.loanType);
|
||||
map.put("recalculationFrequencyType", this.recalculationFrequencyType);
|
||||
if (charges != null) {
|
||||
map.put("charges", charges);
|
||||
}
|
||||
|
||||
+14
-1
@@ -58,12 +58,18 @@ public class LoanProductTestBuilder {
|
||||
private Boolean multiDisburseLoan = false;
|
||||
private String outstandingLoanBalance = "35000";
|
||||
private String maxTrancheCount = "35000";
|
||||
|
||||
private Boolean isInterestRecalculationEnabled = false;
|
||||
private String daysInYearType = "1";
|
||||
private String daysInMonthType = "1";
|
||||
private String interestRecalculationCompoundingMethod = "0";
|
||||
private String rescheduleStrategyMethod = "1";
|
||||
|
||||
public String build(final String chargeId) {
|
||||
final HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
if (chargeId != null) {
|
||||
List<HashMap<String, String>> charges = new ArrayList<HashMap<String, String>>();
|
||||
List<HashMap<String, String>> charges = new ArrayList<>();
|
||||
HashMap<String, String> chargeMap = new HashMap<>();
|
||||
chargeMap.put("id", chargeId);
|
||||
charges.add(chargeMap);
|
||||
@@ -101,6 +107,13 @@ public class LoanProductTestBuilder {
|
||||
} else if (this.accountingRule.equals(CASH_BASED)) {
|
||||
map.putAll(getAccountMappingForCashBased());
|
||||
}
|
||||
map.put("daysInMonthType", this.daysInMonthType);
|
||||
map.put("daysInYearType", this.daysInYearType);
|
||||
map.put("isInterestRecalculationEnabled", this.isInterestRecalculationEnabled);
|
||||
if (this.isInterestRecalculationEnabled) {
|
||||
map.put("interestRecalculationCompoundingMethod", this.interestRecalculationCompoundingMethod);
|
||||
map.put("rescheduleStrategyMethod", this.rescheduleStrategyMethod);
|
||||
}
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -501,4 +501,15 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void updateStartAndEndDate(final LocalDate startDate, final LocalDate endDate) {
|
||||
|
||||
final CalendarFrequencyType frequencyType = CalendarUtils.getFrequency(this.recurrence);
|
||||
final Integer interval = new Integer(CalendarUtils.getInterval(this.recurrence));
|
||||
final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, startDate.getDayOfWeek());
|
||||
|
||||
this.recurrence = newRecurrence;
|
||||
this.startDate = startDate.toDate();
|
||||
this.endDate = endDate.toDate();
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -10,8 +10,13 @@ import java.util.Map;
|
||||
|
||||
public enum CalendarEntityType {
|
||||
|
||||
INVALID(0, "calendarEntityType.invalid"), CLIENTS(1, "calendarEntityType.clients"), GROUPS(2, "calendarEntityType.groups"), LOANS(3,
|
||||
"calendarEntityType.loans"), CENTERS(4, "calendarEntityType.centers"), SAVINGS(5, "calendarEntityType.savings");
|
||||
INVALID(0, "calendarEntityType.invalid"), //
|
||||
CLIENTS(1, "calendarEntityType.clients"), //
|
||||
GROUPS(2, "calendarEntityType.groups"), //
|
||||
LOANS(3, "calendarEntityType.loans"), //
|
||||
CENTERS(4, "calendarEntityType.centers"), //
|
||||
SAVINGS(5, "calendarEntityType.savings"), //
|
||||
LOAN_RECALCULATION_DETAIL(6, "calendarEntityType.loan.recalculation.detail");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
+66
@@ -5,9 +5,24 @@ import java.util.List;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
import org.mifosplatform.portfolio.common.domain.ConditionType;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInMonthType;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInYearType;
|
||||
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
|
||||
public class CommonEnumerations {
|
||||
|
||||
public static final String DAYS_IN_MONTH_TYPE = "daysInMonthType";
|
||||
public static final String DAYS_IN_YEAR_TYPE = "daysInYearType";
|
||||
|
||||
public static EnumOptionData commonEnumueration(final String typeName, final int id) {
|
||||
EnumOptionData enumData = null;
|
||||
if (typeName.equals(DAYS_IN_MONTH_TYPE)) {
|
||||
enumData = daysInMonthType(id);
|
||||
} else if (typeName.equals(DAYS_IN_YEAR_TYPE)) {
|
||||
enumData = daysInYearType(id);
|
||||
}
|
||||
return enumData;
|
||||
}
|
||||
|
||||
public static EnumOptionData termFrequencyType(final int id, final String codePrefix) {
|
||||
return termFrequencyType(PeriodFrequencyType.fromInt(id), codePrefix);
|
||||
@@ -80,4 +95,55 @@ public class CommonEnumerations {
|
||||
return optionDatas;
|
||||
}
|
||||
|
||||
public static EnumOptionData daysInMonthType(final int id) {
|
||||
return daysInMonthType(DaysInMonthType.fromInt(id));
|
||||
}
|
||||
|
||||
public static EnumOptionData daysInMonthType(final DaysInMonthType type) {
|
||||
EnumOptionData optionData = null;
|
||||
switch (type) {
|
||||
case ACTUAL:
|
||||
optionData = new EnumOptionData(DaysInMonthType.ACTUAL.getValue().longValue(), DaysInMonthType.ACTUAL.getCode(), "Actual");
|
||||
break;
|
||||
case DAYS_30:
|
||||
optionData = new EnumOptionData(DaysInMonthType.DAYS_30.getValue().longValue(), DaysInMonthType.DAYS_30.getCode(),
|
||||
"30 Days");
|
||||
break;
|
||||
default:
|
||||
optionData = new EnumOptionData(DaysInMonthType.INVALID.getValue().longValue(), DaysInMonthType.INVALID.getCode(),
|
||||
"Invalid");
|
||||
break;
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData daysInYearType(final int id) {
|
||||
return daysInYearType(DaysInYearType.fromInt(id));
|
||||
}
|
||||
|
||||
public static EnumOptionData daysInYearType(final DaysInYearType type) {
|
||||
EnumOptionData optionData = null;
|
||||
switch (type) {
|
||||
case ACTUAL:
|
||||
optionData = new EnumOptionData(DaysInYearType.ACTUAL.getValue().longValue(), DaysInYearType.ACTUAL.getCode(), "Actual");
|
||||
break;
|
||||
case DAYS_360:
|
||||
optionData = new EnumOptionData(DaysInYearType.DAYS_360.getValue().longValue(), DaysInYearType.DAYS_360.getCode(),
|
||||
"360 Days");
|
||||
break;
|
||||
case DAYS_364:
|
||||
optionData = new EnumOptionData(DaysInYearType.DAYS_364.getValue().longValue(), DaysInYearType.DAYS_364.getCode(),
|
||||
"364 Days");
|
||||
break;
|
||||
case DAYS_365:
|
||||
optionData = new EnumOptionData(DaysInYearType.DAYS_365.getValue().longValue(), DaysInYearType.DAYS_365.getCode(),
|
||||
"365 Days");
|
||||
break;
|
||||
default:
|
||||
optionData = new EnumOptionData(DaysInYearType.INVALID.getValue().longValue(), DaysInYearType.INVALID.getCode(), "Invalid");
|
||||
break;
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
-2
@@ -4,10 +4,13 @@ import java.util.List;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
|
||||
|
||||
public interface DropdownReadPlatformService {
|
||||
|
||||
List<EnumOptionData> retrievePeriodFrequencyTypeOptions();
|
||||
|
||||
|
||||
List<EnumOptionData> retrieveConditionTypeOptions();
|
||||
|
||||
List<EnumOptionData> retrieveDaysInMonthTypeOptions();
|
||||
|
||||
List<EnumOptionData> retrieveDaysInYearTypeOptions();
|
||||
}
|
||||
|
||||
+20
@@ -2,12 +2,16 @@ package org.mifosplatform.portfolio.common.service;
|
||||
|
||||
import static org.mifosplatform.portfolio.common.service.CommonEnumerations.conditionType;
|
||||
import static org.mifosplatform.portfolio.common.service.CommonEnumerations.termFrequencyType;
|
||||
import static org.mifosplatform.portfolio.common.service.CommonEnumerations.daysInMonthType;
|
||||
import static org.mifosplatform.portfolio.common.service.CommonEnumerations.daysInYearType;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
import org.mifosplatform.portfolio.common.domain.ConditionType;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInMonthType;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInYearType;
|
||||
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -30,4 +34,20 @@ public class DropdownReadPlatformServiceImpl implements DropdownReadPlatformServ
|
||||
return loanTermFrequencyOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnumOptionData> retrieveDaysInMonthTypeOptions() {
|
||||
|
||||
final List<EnumOptionData> daysInMonthTypeOptions = Arrays.asList(daysInMonthType(DaysInMonthType.ACTUAL),
|
||||
daysInMonthType(DaysInMonthType.DAYS_30));
|
||||
return daysInMonthTypeOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnumOptionData> retrieveDaysInYearTypeOptions() {
|
||||
|
||||
final List<EnumOptionData> daysInYearTypeOptions = Arrays.asList(daysInYearType(DaysInYearType.ACTUAL),
|
||||
daysInYearType(DaysInYearType.DAYS_360), daysInYearType(DaysInYearType.DAYS_364), daysInYearType(DaysInYearType.DAYS_365));
|
||||
return daysInYearTypeOptions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-1
@@ -23,5 +23,13 @@ public interface LoanApiConstants {
|
||||
public static final String principalDisbursedParameterName = "transactionAmount";
|
||||
|
||||
public static final String ALREADY_DISBURSED = "can.not.change.disbursement.date";
|
||||
|
||||
|
||||
// Interest recalculation related
|
||||
public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled";
|
||||
public static final String daysInYearTypeParameterName = "daysInYearType";
|
||||
public static final String daysInMonthTypeParameterName = "daysInMonthType";
|
||||
public static final String recalculationFrequencyTypeParameterName = "recalculationFrequencyType";
|
||||
public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
|
||||
public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
|
||||
|
||||
}
|
||||
|
||||
+17
-2
@@ -9,6 +9,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -50,6 +51,7 @@ import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
|
||||
import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService;
|
||||
import org.mifosplatform.portfolio.account.service.PortfolioAccountReadPlatformService;
|
||||
import org.mifosplatform.portfolio.calendar.data.CalendarData;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
|
||||
import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService;
|
||||
import org.mifosplatform.portfolio.charge.data.ChargeData;
|
||||
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
|
||||
@@ -290,7 +292,16 @@ public class LoansApiResource {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
|
||||
|
||||
final LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId);
|
||||
LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId);
|
||||
if (loanBasicDetails.isInterestRecalculationEnabled()) {
|
||||
Collection<CalendarData> interestRecalculationCalendarDatas = this.calendarReadPlatformService.retrieveCalendarsByEntity(
|
||||
loanBasicDetails.getInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), null);
|
||||
CalendarData calendarData = null;
|
||||
if (!CollectionUtils.isEmpty(interestRecalculationCalendarDatas)) {
|
||||
calendarData = interestRecalculationCalendarDatas.iterator().next();
|
||||
}
|
||||
loanBasicDetails = LoanAccountData.withInterestRecalculationCalendarData(loanBasicDetails, calendarData);
|
||||
}
|
||||
|
||||
Collection<LoanTransactionData> loanRepayments = null;
|
||||
LoanScheduleData repaymentSchedule = null;
|
||||
@@ -399,6 +410,7 @@ public class LoansApiResource {
|
||||
Collection<CodeValueData> loanCollateralOptions = null;
|
||||
Collection<CalendarData> calendarOptions = null;
|
||||
Collection<PortfolioAccountData> accountLinkingOptions = null;
|
||||
List<EnumOptionData> interestRecalculationFrequencyTypeOptions = null;
|
||||
|
||||
final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters());
|
||||
if (template) {
|
||||
@@ -440,6 +452,9 @@ public class LoansApiResource {
|
||||
calendarOptions = this.loanReadPlatformService.retrieveCalendars(loanBasicDetails.groupId());
|
||||
}
|
||||
|
||||
interestRecalculationFrequencyTypeOptions = this.dropdownReadPlatformService
|
||||
.retrieveInterestRecalculationFrequencyTypeOptions();
|
||||
|
||||
}
|
||||
|
||||
Collection<ChargeData> overdueCharges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanBasicDetails.loanProductId(),
|
||||
@@ -450,7 +465,7 @@ public class LoansApiResource {
|
||||
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
|
||||
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
|
||||
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
|
||||
overdueCharges);
|
||||
overdueCharges, interestRecalculationFrequencyTypeOptions);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
|
||||
mandatoryResponseParameters);
|
||||
|
||||
+111
-17
@@ -14,6 +14,7 @@ import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.Transient;
|
||||
@@ -129,6 +130,7 @@ public class LoanAccountData {
|
||||
private final Collection<ChargeData> chargeOptions;
|
||||
private final Collection<CodeValueData> loanCollateralOptions;
|
||||
private final Collection<CalendarData> calendarOptions;
|
||||
private final List<EnumOptionData> interestRecalculationFrequencyTypeOptions;
|
||||
|
||||
@Transient
|
||||
private final BigDecimal feeChargesAtDisbursementCharged;
|
||||
@@ -162,6 +164,12 @@ public class LoanAccountData {
|
||||
|
||||
private final Collection<ChargeData> overdueCharges;
|
||||
|
||||
private final EnumOptionData daysInMonthType;
|
||||
private final EnumOptionData daysInYearType;
|
||||
private final boolean isInterestRecalculationEnabled;
|
||||
|
||||
private final LoanInterestRecalculationData interestRecalculationData;
|
||||
|
||||
/**
|
||||
* Used to produce a {@link LoanAccountData} with only collateral options
|
||||
* for now.
|
||||
@@ -248,6 +256,13 @@ public class LoanAccountData {
|
||||
final Boolean inArrears = null;
|
||||
final Boolean isNPA = null;
|
||||
final Collection<ChargeData> overdueCharges = null;
|
||||
|
||||
final EnumOptionData daysInMonthType = null;
|
||||
final EnumOptionData daysInYearType = null;
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanInterestRecalculationData interestRecalculationData = null;
|
||||
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
currencyData, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType,
|
||||
@@ -261,7 +276,8 @@ public class LoanAccountData {
|
||||
chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions,
|
||||
syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount,
|
||||
disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations,
|
||||
product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA);
|
||||
product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData, interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -351,6 +367,12 @@ public class LoanAccountData {
|
||||
final Boolean isNPA = null;
|
||||
final Collection<ChargeData> overdueCharges = null;
|
||||
|
||||
final EnumOptionData daysInMonthType = null;
|
||||
final EnumOptionData daysInYearType = null;
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanInterestRecalculationData interestRecalculationData = null;
|
||||
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
currencyData, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType,
|
||||
@@ -364,7 +386,8 @@ public class LoanAccountData {
|
||||
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
|
||||
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount,
|
||||
maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges,
|
||||
isNPA);
|
||||
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData,
|
||||
interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
|
||||
@@ -386,7 +409,8 @@ public class LoanAccountData {
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA);
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -477,6 +501,12 @@ public class LoanAccountData {
|
||||
final Boolean isNPA = null;
|
||||
final Collection<ChargeData> overdueCharges = null;
|
||||
|
||||
final EnumOptionData daysInMonthType = null;
|
||||
final EnumOptionData daysInYearType = null;
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanInterestRecalculationData interestRecalculationData = null;
|
||||
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
currencyData, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType,
|
||||
@@ -489,7 +519,8 @@ public class LoanAccountData {
|
||||
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
|
||||
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
|
||||
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingBalance,
|
||||
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA);
|
||||
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
|
||||
@@ -510,7 +541,8 @@ public class LoanAccountData {
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA);
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
|
||||
@@ -520,7 +552,7 @@ public class LoanAccountData {
|
||||
final Collection<EnumOptionData> interestTypeOptions, final Collection<EnumOptionData> interestCalculationPeriodTypeOptions,
|
||||
final Collection<FundData> fundOptions, final Collection<ChargeData> chargeOptions,
|
||||
final Collection<CodeValueData> loanPurposeOptions, final Collection<CodeValueData> loanCollateralOptions,
|
||||
final Integer loanCycleNumber) {
|
||||
final Integer loanCycleNumber, final List<EnumOptionData> interestRecalculationFrequencyTypeOptions) {
|
||||
|
||||
final Long id = null;
|
||||
final String accountNo = null;
|
||||
@@ -634,7 +666,9 @@ public class LoanAccountData {
|
||||
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
|
||||
notes, accountLinkingOptions, linkedAccount, disbursementData, product.getMultiDisburseLoan(), fixedEmi,
|
||||
product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears,
|
||||
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA);
|
||||
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(),
|
||||
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
|
||||
interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
|
||||
@@ -668,7 +702,7 @@ public class LoanAccountData {
|
||||
for (final ChargeData charge : product.charges()) {
|
||||
charges.add(charge.toLoanChargeData());
|
||||
}
|
||||
|
||||
|
||||
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.getFundId(),
|
||||
product.getFundName(), acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName,
|
||||
@@ -686,7 +720,9 @@ public class LoanAccountData {
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(), acc.fixedEmiAmount,
|
||||
product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears,
|
||||
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA);
|
||||
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(),
|
||||
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
|
||||
acc.interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -708,7 +744,9 @@ public class LoanAccountData {
|
||||
final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged,
|
||||
final Boolean syncDisbursementWithMeeting, final Integer loancounter, final Integer loanProductCounter,
|
||||
final Boolean multiDisburseLoan, final BigDecimal fixedEmiAmont, final BigDecimal outstandingLoanBalance,
|
||||
final Boolean inArrears, final Integer graceOnArrearsAgeing, final Boolean isNPA) {
|
||||
final Boolean inArrears, final Integer graceOnArrearsAgeing, final Boolean isNPA, final EnumOptionData daysInMonthType,
|
||||
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||
final LoanInterestRecalculationData interestRecalculationData) {
|
||||
|
||||
final LoanScheduleData repaymentSchedule = null;
|
||||
final Collection<LoanTransactionData> transactions = null;
|
||||
@@ -739,6 +777,7 @@ public class LoanAccountData {
|
||||
final Map<Long, LoanBorrowerCycleData> memberVariations = null;
|
||||
final LoanProductData product = null;
|
||||
final Collection<ChargeData> overdueCharges = null;
|
||||
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = null;
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
@@ -752,7 +791,8 @@ public class LoanAccountData {
|
||||
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
|
||||
loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter,
|
||||
notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance,
|
||||
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA);
|
||||
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -772,7 +812,9 @@ public class LoanAccountData {
|
||||
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions,
|
||||
final Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
|
||||
final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges) {
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges,
|
||||
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions) {
|
||||
|
||||
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
|
||||
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
|
||||
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
|
||||
@@ -788,7 +830,8 @@ public class LoanAccountData {
|
||||
loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes,
|
||||
accountLinkingOptions, linkedAccount, disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount,
|
||||
acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
|
||||
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA);
|
||||
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
|
||||
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
|
||||
@@ -799,7 +842,8 @@ 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.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges,
|
||||
acc.interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
|
||||
@@ -819,7 +863,8 @@ public class LoanAccountData {
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA);
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
|
||||
@@ -876,7 +921,32 @@ public class LoanAccountData {
|
||||
acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions,
|
||||
acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance,
|
||||
acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges,
|
||||
acc.isNPA);
|
||||
acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData,
|
||||
acc.interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
|
||||
|
||||
final LoanInterestRecalculationData interestRecalculationData = LoanInterestRecalculationData.withCalendarData(
|
||||
acc.interestRecalculationData, calendarData);
|
||||
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
|
||||
acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
|
||||
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
|
||||
acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType,
|
||||
acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.transactionProcessingStrategyId,
|
||||
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
|
||||
acc.annualInterestRate, acc.interestType, 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.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.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.interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
private LoanAccountData(
|
||||
@@ -930,7 +1000,10 @@ public class LoanAccountData {
|
||||
final Boolean multiDisburseLoan, BigDecimal fixedEmiAmount, final BigDecimal maxOutstandingLoanBalance,
|
||||
final Collection<LoanTermVariationsData> emiAmountVariations, final Map<Long, LoanBorrowerCycleData> memberVariations,
|
||||
final LoanProductData product, final Boolean inArrears, final Integer graceOnArrearsAgeing,
|
||||
final Collection<ChargeData> overdueCharges, final Boolean isNPA) {
|
||||
final Collection<ChargeData> overdueCharges, final Boolean isNPA, final EnumOptionData daysInMonthType,
|
||||
final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
|
||||
final LoanInterestRecalculationData interestRecalculationData,
|
||||
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions) {
|
||||
this.id = id;
|
||||
this.accountNo = accountNo;
|
||||
this.status = status;
|
||||
@@ -1069,6 +1142,18 @@ public class LoanAccountData {
|
||||
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
|
||||
this.overdueCharges = overdueCharges;
|
||||
this.isNPA = isNPA;
|
||||
|
||||
this.daysInMonthType = daysInMonthType;
|
||||
this.daysInYearType = daysInYearType;
|
||||
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
|
||||
this.interestRecalculationData = interestRecalculationData;
|
||||
|
||||
if (CollectionUtils.isEmpty(interestRecalculationFrequencyTypeOptions)) {
|
||||
this.interestRecalculationFrequencyTypeOptions = null;
|
||||
} else {
|
||||
this.interestRecalculationFrequencyTypeOptions = interestRecalculationFrequencyTypeOptions;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
|
||||
@@ -1150,4 +1235,13 @@ public class LoanAccountData {
|
||||
return this.summary.getTotalOutstanding();
|
||||
}
|
||||
|
||||
public boolean isInterestRecalculationEnabled() {
|
||||
return this.isInterestRecalculationEnabled;
|
||||
}
|
||||
|
||||
public Long getInterestRecalculationDetailId() {
|
||||
if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getId(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.data;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
import org.mifosplatform.portfolio.calendar.data.CalendarData;
|
||||
|
||||
public class LoanInterestRecalculationData {
|
||||
|
||||
private final Long id;
|
||||
private final Long loanId;
|
||||
private final EnumOptionData interestRecalculationCompoundingType;
|
||||
private final EnumOptionData rescheduleStrategyType;
|
||||
@SuppressWarnings("unused")
|
||||
private final CalendarData calendarData;
|
||||
|
||||
public LoanInterestRecalculationData(final Long id, final Long loanId, final EnumOptionData interestRecalculationCompoundingType,
|
||||
final EnumOptionData rescheduleStrategyType, final CalendarData calendarData) {
|
||||
this.id = id;
|
||||
this.loanId = loanId;
|
||||
this.interestRecalculationCompoundingType = interestRecalculationCompoundingType;
|
||||
this.rescheduleStrategyType = rescheduleStrategyType;
|
||||
this.calendarData = calendarData;
|
||||
}
|
||||
|
||||
public static LoanInterestRecalculationData withCalendarData(final LoanInterestRecalculationData recalculationData,
|
||||
final CalendarData calendarData) {
|
||||
return new LoanInterestRecalculationData(recalculationData.id, recalculationData.loanId,
|
||||
recalculationData.interestRecalculationCompoundingType, recalculationData.rescheduleStrategyType, calendarData);
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -306,6 +306,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
@Column(name = "total_recovered_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalRecovered;
|
||||
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true)
|
||||
private LoanInterestRecalculationDetails loanInterestRecalculationDetails;
|
||||
|
||||
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
|
||||
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
@@ -1261,6 +1265,28 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
private boolean isInterestRecalculationEnabledForProduct() {
|
||||
return this.loanProduct.isInterestRecalculationEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update interest recalculation settings if product configuration changes
|
||||
*/
|
||||
public void updateLoanInterestRecalculationSettings() {
|
||||
|
||||
if (isInterestRecalculationEnabledForProduct()) {
|
||||
if (this.loanInterestRecalculationDetails == null) {
|
||||
this.loanInterestRecalculationDetails = this.loanProduct.copyInterestRecalculationSettings();
|
||||
this.loanInterestRecalculationDetails.updateLoan(this);
|
||||
} else {
|
||||
this.loanInterestRecalculationDetails = this.loanProduct
|
||||
.updateLoanInterestRecalculationDetails(this.loanInterestRecalculationDetails);
|
||||
}
|
||||
} else {
|
||||
this.loanInterestRecalculationDetails = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateLoanCharge(final LoanCharge loanCharge) {
|
||||
final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
|
||||
BigDecimal chargeAmt = BigDecimal.ZERO;
|
||||
@@ -1483,6 +1509,15 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
// validate if disbursement date is a holiday or a non-working day
|
||||
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
|
||||
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
|
||||
|
||||
/**
|
||||
* Copy interest recalculation settings if interest recalculation is enabled
|
||||
*/
|
||||
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
|
||||
this.loanInterestRecalculationDetails = this.loanProduct.copyInterestRecalculationSettings();
|
||||
this.loanInterestRecalculationDetails.updateLoan(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private LocalDate determineExpectedMaturityDate() {
|
||||
@@ -3852,4 +3887,33 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public BigDecimal getTotalOverpaid() {
|
||||
return this.totalOverpaid;
|
||||
}
|
||||
|
||||
public void updateIsInterestRecalculationEnabled() {
|
||||
this.loanRepaymentScheduleDetail.updateIsInterestRecalculationEnabled(isInterestRecalculationEnabledForProduct());
|
||||
}
|
||||
|
||||
public LoanInterestRecalculationDetails loanInterestRecalculationDetails() {
|
||||
return this.loanInterestRecalculationDetails;
|
||||
}
|
||||
|
||||
public Long loanInterestRecalculationDetailId() {
|
||||
if (loanInterestRecalculationDetails() != null) { return this.loanInterestRecalculationDetails.getId(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedMaturityDate() {
|
||||
LocalDate expectedMaturityDate = null;
|
||||
if (this.expectedMaturityDate != null) {
|
||||
expectedMaturityDate = new LocalDate(this.expectedMaturityDate);
|
||||
}
|
||||
return expectedMaturityDate;
|
||||
}
|
||||
|
||||
public LocalDate getMaturityDate() {
|
||||
LocalDate maturityDate = getExpectedMaturityDate();
|
||||
if (this.actualMaturityDate != null) {
|
||||
maturityDate = new LocalDate(this.actualMaturityDate);
|
||||
}
|
||||
return maturityDate;
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
/**
|
||||
* Entity for holding interest recalculation setting, details will be copied
|
||||
* from product directly
|
||||
*
|
||||
* @author conflux
|
||||
*/
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_loan_recalculation_details")
|
||||
public class LoanInterestRecalculationDetails extends AbstractPersistable<Long> {
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "loan_id", nullable = false)
|
||||
private Loan loan;
|
||||
|
||||
/**
|
||||
* {@link InterestRecalculationCompoundingMethod}
|
||||
*/
|
||||
@Column(name = "compound_type_enum", nullable = false)
|
||||
private Integer interestRecalculationCompoundingMethod;
|
||||
|
||||
/**
|
||||
* {@link LoanRescheduleStrategyMethod}
|
||||
*/
|
||||
@Column(name = "reschedule_strategy_enum", nullable = false)
|
||||
private Integer rescheduleStrategyMethod;
|
||||
|
||||
protected LoanInterestRecalculationDetails() {
|
||||
// Default constructor for jpa repository
|
||||
}
|
||||
|
||||
private LoanInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod) {
|
||||
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
|
||||
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
|
||||
}
|
||||
|
||||
public static LoanInterestRecalculationDetails createFrom(final Integer interestRecalculationCompoundingMethod,
|
||||
final Integer rescheduleStrategyMethod) {
|
||||
return new LoanInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod);
|
||||
}
|
||||
|
||||
public void updateLoan(final Loan loan) {
|
||||
this.loan = loan;
|
||||
}
|
||||
|
||||
public void update(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod) {
|
||||
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
|
||||
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
|
||||
}
|
||||
|
||||
}
|
||||
+15
-5
@@ -107,6 +107,8 @@ public final class LoanApplicationTerms {
|
||||
private final DaysInMonthType daysInMonthType;
|
||||
|
||||
private final DaysInYearType daysInYearType;
|
||||
|
||||
private final boolean isInterestRecalculationEnabled;
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
|
||||
@@ -120,14 +122,14 @@ public final class LoanApplicationTerms {
|
||||
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 DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled) {
|
||||
|
||||
return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
|
||||
repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType,
|
||||
annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate,
|
||||
repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas,
|
||||
maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType);
|
||||
maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled);
|
||||
}
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
|
||||
@@ -154,13 +156,18 @@ public final class LoanApplicationTerms {
|
||||
final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment();
|
||||
final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged();
|
||||
|
||||
// Interest recalculation settings
|
||||
final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType();
|
||||
final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType();
|
||||
final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled();
|
||||
|
||||
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
|
||||
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
|
||||
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
|
||||
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
|
||||
disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(),
|
||||
loanProductRelatedDetail.fetchDaysInMonthType(), loanProductRelatedDetail.fetchDaysInYearType());
|
||||
daysInMonthType, daysInYearType, isInterestRecalculationEnabled);
|
||||
}
|
||||
|
||||
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
@@ -174,7 +181,8 @@ public final class LoanApplicationTerms {
|
||||
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 Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
|
||||
final boolean isInterestRecalculationEnabled) {
|
||||
this.currency = currency;
|
||||
this.loanTermFrequency = loanTermFrequency;
|
||||
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
|
||||
@@ -208,6 +216,7 @@ public final class LoanApplicationTerms {
|
||||
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
|
||||
this.daysInMonthType = daysInMonthType;
|
||||
this.daysInYearType = daysInYearType;
|
||||
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
|
||||
}
|
||||
|
||||
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
|
||||
@@ -752,7 +761,8 @@ public final class LoanApplicationTerms {
|
||||
this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod,
|
||||
this.interestCalculationPeriodMethod, this.repaymentEvery, this.repaymentPeriodFrequencyType, this.numberOfRepayments,
|
||||
this.principalGrace, this.interestPaymentGrace, this.interestChargingGrace, this.amortizationMethod,
|
||||
this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing);
|
||||
this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, this.daysInMonthType.getValue(),
|
||||
this.daysInYearType.getValue(), this.isInterestRecalculationEnabled);
|
||||
}
|
||||
|
||||
public Integer getLoanTermFrequency() {
|
||||
|
||||
+12
-1
@@ -35,6 +35,8 @@ import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchEx
|
||||
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
|
||||
import org.mifosplatform.portfolio.client.domain.Client;
|
||||
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInMonthType;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInYearType;
|
||||
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.group.domain.Group;
|
||||
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
|
||||
@@ -215,13 +217,22 @@ public class LoanScheduleAssembler {
|
||||
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount);
|
||||
loanVariationTermsData.add(data);
|
||||
|
||||
/**
|
||||
* Interest recalculation settings copy from product definition
|
||||
*/
|
||||
final DaysInMonthType daysInMonthType = loanProduct.fetchDaysInMonthType();
|
||||
|
||||
final DaysInYearType daysInYearType = loanProduct.fetchDaysInYearType();
|
||||
|
||||
final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled();
|
||||
|
||||
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
|
||||
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
|
||||
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
|
||||
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney,
|
||||
loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData,
|
||||
graceOnArrearsAgeing, loanProduct.fetchDaysInMonthType(), loanProduct.fetchDaysInYearType());
|
||||
graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled);
|
||||
}
|
||||
|
||||
private List<DisbursementData> fetchDisbursementData(final JsonObject command) {
|
||||
|
||||
+9
-9
@@ -33,15 +33,15 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
|
||||
/**
|
||||
* The parameters supported for this command.
|
||||
*/
|
||||
final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanType", "calendarId",
|
||||
"productId", "accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId",
|
||||
"principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency",
|
||||
"loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType",
|
||||
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment",
|
||||
"graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale",
|
||||
"dateFormat", "charges", "collateral", "syncDisbursementWithMeeting", "linkAccountId",
|
||||
LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName,
|
||||
LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName));
|
||||
final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanType", "calendarId", "productId",
|
||||
"accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId", "principal",
|
||||
"inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency",
|
||||
"loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType",
|
||||
"expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment", "graceOnInterestPayment",
|
||||
"graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", "charges",
|
||||
"collateral", "syncDisbursementWithMeeting", "linkAccountId", LoanApiConstants.disbursementDataParameterName,
|
||||
LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName,
|
||||
LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.recalculationFrequencyTypeParameterName));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
|
||||
+19
-1
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.serialization;
|
||||
|
||||
import static org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants.recalculationFrequencyTypeParameterName;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
@@ -59,7 +61,8 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
"calendarId", // optional
|
||||
"syncDisbursementWithMeeting",// optional
|
||||
"linkAccountId", LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName,
|
||||
LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName));
|
||||
LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName,
|
||||
LoanApiConstants.recalculationFrequencyTypeParameterName));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper;
|
||||
@@ -357,6 +360,13 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
}
|
||||
validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal);
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.recalculationFrequencyTypeParameterName, element)) {
|
||||
final Integer recalculationFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(
|
||||
LoanApiConstants.recalculationFrequencyTypeParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanApiConstants.recalculationFrequencyTypeParameterName).value(recalculationFrequencyType)
|
||||
.notNull().inMinMaxRange(1, 5);
|
||||
}
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||
}
|
||||
|
||||
@@ -829,4 +839,12 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
|
||||
}
|
||||
|
||||
public void validateRecalcuationFrequencyType(final Integer recalculationFrequencyTypeValue) {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
|
||||
baseDataValidator.reset().parameter(recalculationFrequencyTypeParameterName).value(recalculationFrequencyTypeValue).notNull()
|
||||
.inMinMaxRange(1, 5);
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||
}
|
||||
|
||||
}
|
||||
+125
@@ -5,6 +5,8 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.service;
|
||||
|
||||
import static org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants.recalculationFrequencyTypeParameterName;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -31,9 +33,11 @@ import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository;
|
||||
import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
|
||||
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;
|
||||
import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator;
|
||||
import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory;
|
||||
@@ -70,6 +74,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType;
|
||||
import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException;
|
||||
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
|
||||
import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductDataValidator;
|
||||
@@ -206,6 +211,15 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
|
||||
this.loanRepository.save(newLoanApplication);
|
||||
|
||||
if (loanProduct.isInterestRecalculationEnabled()) {
|
||||
|
||||
final Integer recalculationFrequencyTypeValue = command
|
||||
.integerValueOfParameterNamed(recalculationFrequencyTypeParameterName);
|
||||
this.fromApiJsonDeserializer.validateRecalcuationFrequencyType(recalculationFrequencyTypeValue);
|
||||
|
||||
createAndPersistCalendarInstanceForInterestRecalculation(newLoanApplication, recalculationFrequencyTypeValue);
|
||||
}
|
||||
|
||||
if (newLoanApplication.isAccountNumberRequiresAutoGeneration()) {
|
||||
final AccountNumberGenerator accountNoGenerator = this.accountIdentifierGeneratorFactory
|
||||
.determineLoanAccountNoGenerator(newLoanApplication.getId());
|
||||
@@ -256,6 +270,88 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
}
|
||||
}
|
||||
|
||||
private void createAndPersistCalendarInstanceForInterestRecalculation(final Loan loan, final Integer recalculationFrequencyTypeValue) {
|
||||
|
||||
final LocalDate calendarStartDate = loan.getDisbursementDate();
|
||||
final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
|
||||
final RecalculationFrequencyType recalculationFrequencyType = RecalculationFrequencyType.fromInt(recalculationFrequencyTypeValue);
|
||||
|
||||
Integer frequency = 1;
|
||||
CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID;
|
||||
switch (recalculationFrequencyType) {
|
||||
case DAILY:
|
||||
frequency = 1;
|
||||
calendarFrequencyType = CalendarFrequencyType.DAILY;
|
||||
break;
|
||||
case FORTNIGHTLY:
|
||||
frequency = 2;
|
||||
calendarFrequencyType = CalendarFrequencyType.WEEKLY;
|
||||
break;
|
||||
case MONTHLY:
|
||||
frequency = 1;
|
||||
calendarFrequencyType = CalendarFrequencyType.MONTHLY;
|
||||
break;
|
||||
case SAME_AS_REPAYMENT_PERIOD:
|
||||
frequency = loan.repaymentScheduleDetail().getRepayEvery();
|
||||
calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType());
|
||||
break;
|
||||
case WEEKLY:
|
||||
frequency = 1;
|
||||
calendarFrequencyType = CalendarFrequencyType.WEEKLY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
final String title = "loan_recalculation_detail_" + loan.loanInterestRecalculationDetails().getId();
|
||||
final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(),
|
||||
calendarFrequencyType, frequency, repeatsOnDay);
|
||||
final CalendarInstance calendarInstance = CalendarInstance.from(calendar, loan.loanInterestRecalculationDetails().getId(),
|
||||
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
|
||||
this.calendarInstanceRepository.save(calendarInstance);
|
||||
|
||||
}
|
||||
|
||||
private void updateCalendarDetailsForInterestRecalculation(final CalendarInstance calendarInstance, final Loan loan,
|
||||
final Integer recalculationFrequencyTypeValue) {
|
||||
|
||||
Calendar interestRecalculationRecurrings = calendarInstance.getCalendar();
|
||||
final LocalDate calendarStartDate = loan.getDisbursementDate();
|
||||
final Integer repeatsOnDay = calendarStartDate.getDayOfWeek();
|
||||
final RecalculationFrequencyType recalculationFrequencyType = RecalculationFrequencyType.fromInt(recalculationFrequencyTypeValue);
|
||||
|
||||
Integer frequency = 1;
|
||||
CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID;
|
||||
switch (recalculationFrequencyType) {
|
||||
case DAILY:
|
||||
frequency = 1;
|
||||
calendarFrequencyType = CalendarFrequencyType.DAILY;
|
||||
break;
|
||||
case FORTNIGHTLY:
|
||||
frequency = 2;
|
||||
calendarFrequencyType = CalendarFrequencyType.WEEKLY;
|
||||
break;
|
||||
case MONTHLY:
|
||||
frequency = 1;
|
||||
calendarFrequencyType = CalendarFrequencyType.MONTHLY;
|
||||
break;
|
||||
case SAME_AS_REPAYMENT_PERIOD:
|
||||
frequency = loan.repaymentScheduleDetail().getRepayEvery();
|
||||
calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType());
|
||||
break;
|
||||
case WEEKLY:
|
||||
frequency = 1;
|
||||
calendarFrequencyType = CalendarFrequencyType.WEEKLY;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
interestRecalculationRecurrings.updateRepeatingCalendar(calendarStartDate, calendarFrequencyType, frequency, repeatsOnDay);
|
||||
this.calendarRepository.save(interestRecalculationRecurrings);
|
||||
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) {
|
||||
@@ -369,7 +465,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||
}
|
||||
|
||||
existingLoanApplication.updateIsInterestRecalculationEnabled();
|
||||
validateSubmittedOnDate(existingLoanApplication);
|
||||
|
||||
final LoanProductRelatedDetail productRelatedDetail = existingLoanApplication.repaymentScheduleDetail();
|
||||
this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(),
|
||||
existingLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(),
|
||||
@@ -528,6 +626,33 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
}
|
||||
}
|
||||
|
||||
// updating loan interest recalculation details throwing null
|
||||
// pointer exception after saveAndFlush
|
||||
// http://stackoverflow.com/questions/17151757/hibernate-cascade-update-gives-null-pointer/17334374#17334374
|
||||
existingLoanApplication.updateLoanInterestRecalculationSettings();
|
||||
this.loanRepository.save(existingLoanApplication);
|
||||
|
||||
if (productRelatedDetail.isInterestRecalculationEnabled()) {
|
||||
|
||||
final Integer recalculationFrequencyTypeValue = command
|
||||
.integerValueOfParameterNamed(recalculationFrequencyTypeParameterName);
|
||||
|
||||
this.fromApiJsonDeserializer.validateRecalcuationFrequencyType(recalculationFrequencyTypeValue);
|
||||
|
||||
CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId(
|
||||
existingLoanApplication.loanInterestRecalculationDetailId(),
|
||||
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), CalendarType.COLLECTION.getValue());
|
||||
|
||||
if (calendarInstance == null) {
|
||||
createAndPersistCalendarInstanceForInterestRecalculation(existingLoanApplication, recalculationFrequencyTypeValue);
|
||||
|
||||
} else {
|
||||
updateCalendarDetailsForInterestRecalculation(calendarInstance, existingLoanApplication,
|
||||
recalculationFrequencyTypeValue);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withEntityId(loanId) //
|
||||
.withOfficeId(existingLoanApplication.getOfficeId()) //
|
||||
|
||||
+35
-3
@@ -50,6 +50,7 @@ import org.mifosplatform.portfolio.client.data.ClientData;
|
||||
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.fund.data.FundData;
|
||||
import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
|
||||
import org.mifosplatform.portfolio.group.data.GroupGeneralData;
|
||||
@@ -59,6 +60,7 @@ import org.mifosplatform.portfolio.group.service.SearchParameters;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanApplicationTimelineData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanInterestRecalculationData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanSummaryData;
|
||||
@@ -499,9 +501,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
+ " la.overdue_since_date_derived as overdueSinceDate,"
|
||||
+ " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting,"
|
||||
+ " l.loan_counter as loanCounter, l.loan_product_counter as loanProductCounter,"
|
||||
+ " l.is_npa as isNPA "
|
||||
+ " l.is_npa as isNPA, l.days_in_month_enum as daysInMonth, l.days_in_year_enum as daysInYear, "
|
||||
+ " l.interest_recalculation_enabled as isInterestRecalculationEnabled, "
|
||||
+ " lir.id as lirId, lir.loan_id as loanId, lir.compound_type_enum as compoundType, lir.reschedule_strategy_enum as rescheduleStrategy "
|
||||
+ " 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 "
|
||||
+ " join m_currency rc on rc.`code` = l.currency_code" //
|
||||
+ " left join m_client c on c.id = l.client_id" //
|
||||
+ " left join m_group g on g.id = l.group_id" //
|
||||
@@ -723,6 +728,28 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final BigDecimal fixedEmiAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "fixedEmiAmount");
|
||||
final Boolean isNPA = rs.getBoolean("isNPA");
|
||||
|
||||
final int daysInMonth = JdbcSupport.getInteger(rs, "daysInMonth");
|
||||
final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(daysInMonth);
|
||||
final int daysInYear = JdbcSupport.getInteger(rs, "daysInYear");
|
||||
final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(daysInYear);
|
||||
final boolean isInterestRecalculationEnabled = rs.getBoolean("isInterestRecalculationEnabled");
|
||||
|
||||
LoanInterestRecalculationData interestRecalculationData = null;
|
||||
if (isInterestRecalculationEnabled) {
|
||||
|
||||
final Long lprId = JdbcSupport.getLong(rs, "lirId");
|
||||
final Long productId = JdbcSupport.getLong(rs, "loanId");
|
||||
final int compoundTypeEnumValue = JdbcSupport.getInteger(rs, "compoundType");
|
||||
final EnumOptionData interestRecalculationCompoundingType = LoanEnumerations
|
||||
.interestRecalculationCompoundingType(compoundTypeEnumValue);
|
||||
final int rescheduleStrategyEnumValue = JdbcSupport.getInteger(rs, "rescheduleStrategy");
|
||||
final EnumOptionData rescheduleStrategyType = LoanEnumerations.rescheduleStrategyType(rescheduleStrategyEnumValue);
|
||||
final CalendarData calendarData = null;
|
||||
|
||||
interestRecalculationData = new LoanInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType,
|
||||
rescheduleStrategyType, calendarData);
|
||||
}
|
||||
|
||||
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData,
|
||||
loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName,
|
||||
loanOfficerId, loanOfficerName, currencyData, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance,
|
||||
@@ -731,7 +758,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment,
|
||||
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary,
|
||||
feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan,
|
||||
fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA);
|
||||
fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1161,10 +1189,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
loanCycleCounter = retriveLoanCounter(clientId, loanProduct.getId());
|
||||
}
|
||||
}
|
||||
|
||||
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = this.loanDropdownReadPlatformService
|
||||
.retrieveInterestRecalculationFrequencyTypeOptions();
|
||||
|
||||
return LoanAccountData.loanProductWithTemplateDefaults(loanProduct, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions,
|
||||
repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
|
||||
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions,
|
||||
loanCycleCounter);
|
||||
loanCycleCounter, interestRecalculationFrequencyTypeOptions);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+23
-1
@@ -59,6 +59,8 @@ 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.domain.CalendarRepository;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarType;
|
||||
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
|
||||
import org.mifosplatform.portfolio.charge.domain.Charge;
|
||||
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
|
||||
@@ -165,6 +167,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
private final AccountTransferRepository accountTransferRepository;
|
||||
private final CalendarRepository calendarRepository;
|
||||
|
||||
@Autowired
|
||||
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
|
||||
@@ -183,7 +186,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final AccountTransfersReadPlatformService accountTransfersReadPlatformService,
|
||||
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
|
||||
final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanReadPlatformService loanReadPlatformService,
|
||||
final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository) {
|
||||
final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository,
|
||||
final CalendarRepository calendarRepository) {
|
||||
this.context = context;
|
||||
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
|
||||
this.loanAssembler = loanAssembler;
|
||||
@@ -210,6 +214,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.loanReadPlatformService = loanReadPlatformService;
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.accountTransferRepository = accountTransferRepository;
|
||||
this.calendarRepository = calendarRepository;
|
||||
}
|
||||
|
||||
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
|
||||
@@ -328,6 +333,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
.withCommandId(command.commandId()) //
|
||||
@@ -340,6 +347,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.build();
|
||||
}
|
||||
|
||||
private void updateRecurringCalendarDatesForInterestRecalculation(final Loan loan) {
|
||||
|
||||
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
final CalendarInstance calendarInstanceForInterestRecalculation = this.calendarInstanceRepository
|
||||
.findByEntityIdAndEntityTypeIdAndCalendarTypeId(loan.loanInterestRecalculationDetailId(),
|
||||
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), CalendarType.COLLECTION.getValue());
|
||||
|
||||
Calendar calendarForInterestRecalculation = calendarInstanceForInterestRecalculation.getCalendar();
|
||||
calendarForInterestRecalculation.updateStartAndEndDate(loan.getDisbursementDate(), loan.getMaturityDate());
|
||||
this.calendarRepository.save(calendarForInterestRecalculation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
|
||||
try {
|
||||
this.loanRepository.saveAndFlush(loan);
|
||||
@@ -503,6 +524,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
fromSavingsAccount, isRegularTransaction);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
+9
-2
@@ -52,9 +52,16 @@ public interface LoanProductConstants {
|
||||
public static final String multiDisburseLoanParameterName = "multiDisburseLoan";
|
||||
public static final String maxTrancheCountParameterName = "maxTrancheCount";
|
||||
public static final String outstandingLoanBalanceParameterName = "outstandingLoanBalance";
|
||||
|
||||
|
||||
public static final String graceOnArrearsAgeingParameterName = "graceOnArrearsAgeing";
|
||||
|
||||
|
||||
public static final String overdueDaysForNPAParameterName = "overdueDaysForNPA";
|
||||
|
||||
// Interest recalculation related
|
||||
public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled";
|
||||
public static final String daysInYearTypeParameterName = "daysInYearType";
|
||||
public static final String daysInMonthTypeParameterName = "daysInMonthType";
|
||||
public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
|
||||
public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
|
||||
|
||||
}
|
||||
|
||||
+13
-2
@@ -45,6 +45,7 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService;
|
||||
import org.mifosplatform.portfolio.charge.data.ChargeData;
|
||||
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
|
||||
import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService;
|
||||
import org.mifosplatform.portfolio.fund.data.FundData;
|
||||
import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
|
||||
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
|
||||
@@ -94,6 +95,7 @@ public class LoanProductsApiResource {
|
||||
private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
|
||||
private final DefaultToApiJsonSerializer<ProductMixData> productMixDataApiJsonSerializer;
|
||||
private final ProductMixReadPlatformService productMixReadPlatformService;
|
||||
private final DropdownReadPlatformService commonDropdownReadPlatformService;
|
||||
|
||||
@Autowired
|
||||
public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService,
|
||||
@@ -106,7 +108,8 @@ public class LoanProductsApiResource {
|
||||
final CodeValueReadPlatformService codeValueReadPlatformService,
|
||||
final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService,
|
||||
final DefaultToApiJsonSerializer<ProductMixData> productMixDataApiJsonSerializer,
|
||||
final ProductMixReadPlatformService productMixReadPlatformService) {
|
||||
final ProductMixReadPlatformService productMixReadPlatformService,
|
||||
final DropdownReadPlatformService commonDropdownReadPlatformService) {
|
||||
this.context = context;
|
||||
this.loanProductReadPlatformService = readPlatformService;
|
||||
this.chargeReadPlatformService = chargeReadPlatformService;
|
||||
@@ -121,6 +124,7 @@ public class LoanProductsApiResource {
|
||||
this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService;
|
||||
this.productMixDataApiJsonSerializer = productMixDataApiJsonSerializer;
|
||||
this.productMixReadPlatformService = productMixReadPlatformService;
|
||||
this.commonDropdownReadPlatformService = commonDropdownReadPlatformService;
|
||||
}
|
||||
|
||||
@POST
|
||||
@@ -268,10 +272,17 @@ public class LoanProductsApiResource {
|
||||
final List<EnumOptionData> loanCycleValueConditionTypeOptions = this.dropdownReadPlatformService
|
||||
.retrieveLoanCycleValueConditionTypeOptions();
|
||||
|
||||
final List<EnumOptionData> daysInMonthTypeOptions = commonDropdownReadPlatformService.retrieveDaysInMonthTypeOptions();
|
||||
final List<EnumOptionData> daysInYearTypeOptions = commonDropdownReadPlatformService.retrieveDaysInYearTypeOptions();
|
||||
final List<EnumOptionData> interestRecalculationCompoundingTypeOptions = dropdownReadPlatformService
|
||||
.retrieveInterestRecalculationCompoundingTypeOptions();
|
||||
final List<EnumOptionData> rescheduleStrategyTypeOptions = dropdownReadPlatformService.retrieveRescheduleStrategyTypeOptions();
|
||||
|
||||
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions,
|
||||
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions,
|
||||
interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions,
|
||||
accountingRuleTypeOptions, loanCycleValueConditionTypeOptions);
|
||||
accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions,
|
||||
interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions);
|
||||
}
|
||||
|
||||
}
|
||||
+99
-5
@@ -20,9 +20,14 @@ import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGL
|
||||
import org.mifosplatform.infrastructure.codes.data.CodeValueData;
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.portfolio.calendar.data.CalendarData;
|
||||
import org.mifosplatform.portfolio.charge.data.ChargeData;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInMonthType;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInYearType;
|
||||
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.common.service.CommonEnumerations;
|
||||
import org.mifosplatform.portfolio.fund.data.FundData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanInterestRecalculationData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
|
||||
@@ -73,6 +78,10 @@ public class LoanProductData {
|
||||
private final Integer graceOnInterestCharged;
|
||||
private final Integer graceOnArrearsAgeing;
|
||||
private final Integer overdueDaysForNPA;
|
||||
private final EnumOptionData daysInMonthType;
|
||||
private final EnumOptionData daysInYearType;
|
||||
private final boolean isInterestRecalculationEnabled;
|
||||
private final LoanProductInterestRecalculationData interestRecalculationData;
|
||||
|
||||
// charges
|
||||
private final Collection<ChargeData> charges;
|
||||
@@ -108,6 +117,14 @@ public class LoanProductData {
|
||||
private final Map<String, List<GLAccountData>> accountingMappingOptions;
|
||||
@SuppressWarnings("unused")
|
||||
private final List<EnumOptionData> valueConditionTypeOptions;
|
||||
@SuppressWarnings("unused")
|
||||
private final List<EnumOptionData> daysInMonthTypeOptions;
|
||||
@SuppressWarnings("unused")
|
||||
private final List<EnumOptionData> daysInYearTypeOptions;
|
||||
@SuppressWarnings("unused")
|
||||
private final List<EnumOptionData> interestRecalculationCompoundingTypeOptions;
|
||||
@SuppressWarnings("unused")
|
||||
private final List<EnumOptionData> rescheduleStrategyTypeOptions;
|
||||
|
||||
private final Boolean multiDisburseLoan;
|
||||
private final Integer maxTrancheCount;
|
||||
@@ -160,6 +177,12 @@ public class LoanProductData {
|
||||
final Boolean multiDisburseLoan = null;
|
||||
final Integer maxTrancheCount = null;
|
||||
final BigDecimal outstandingLoanBalance = null;
|
||||
|
||||
final EnumOptionData daysInMonthType = null;
|
||||
final EnumOptionData daysInYearType = null;
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanProductInterestRecalculationData interestRecalculationData = null;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
|
||||
@@ -167,7 +190,8 @@ public class LoanProductData {
|
||||
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
|
||||
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
|
||||
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA);
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData);
|
||||
}
|
||||
|
||||
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
|
||||
@@ -216,6 +240,11 @@ public class LoanProductData {
|
||||
final Integer maxTrancheCount = null;
|
||||
final BigDecimal outstandingLoanBalance = null;
|
||||
|
||||
final EnumOptionData daysInMonthType = null;
|
||||
final EnumOptionData daysInYearType = null;
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanProductInterestRecalculationData interestRecalculationData = null;
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
|
||||
@@ -223,7 +252,8 @@ public class LoanProductData {
|
||||
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
|
||||
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations,
|
||||
interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA);
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData);
|
||||
}
|
||||
|
||||
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
|
||||
@@ -278,6 +308,12 @@ public class LoanProductData {
|
||||
final Integer maxTrancheCount = null;
|
||||
final BigDecimal outstandingLoanBalance = null;
|
||||
|
||||
final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(DaysInMonthType.ACTUAL);
|
||||
final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(DaysInYearType.ACTUAL);
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
final LoanProductInterestRecalculationData interestRecalculationData = LoanProductInterestRecalculationData
|
||||
.sensibleDefaultsForNewLoanProductCreation();
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
|
||||
@@ -285,7 +321,8 @@ public class LoanProductData {
|
||||
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
|
||||
accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId,
|
||||
principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle,
|
||||
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA);
|
||||
multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType,
|
||||
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData);
|
||||
}
|
||||
|
||||
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
|
||||
@@ -313,7 +350,9 @@ public class LoanProductData {
|
||||
Collection<LoanProductBorrowerCycleVariationData> principalVariations,
|
||||
Collection<LoanProductBorrowerCycleVariationData> interestRateVariations,
|
||||
Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations, Boolean multiDisburseLoan,
|
||||
Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, final Integer overdueDaysForNPA) {
|
||||
Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
|
||||
final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType,
|
||||
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.shortName = shortName;
|
||||
@@ -379,6 +418,15 @@ public class LoanProductData {
|
||||
this.maxTrancheCount = maxTrancheCount;
|
||||
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
|
||||
this.overdueDaysForNPA = overdueDaysForNPA;
|
||||
this.daysInMonthType = daysInMonthType;
|
||||
this.daysInYearType = daysInYearType;
|
||||
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
|
||||
this.interestRecalculationData = interestRecalculationData;
|
||||
|
||||
this.daysInMonthTypeOptions = null;
|
||||
this.daysInYearTypeOptions = null;
|
||||
this.interestRecalculationCompoundingTypeOptions = null;
|
||||
this.rescheduleStrategyTypeOptions = null;
|
||||
}
|
||||
|
||||
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
|
||||
@@ -388,7 +436,9 @@ public class LoanProductData {
|
||||
final List<EnumOptionData> repaymentFrequencyTypeOptions, final List<EnumOptionData> interestRateFrequencyTypeOptions,
|
||||
final Collection<FundData> fundOptions, final Collection<TransactionProcessingStrategyData> transactionStrategyOptions,
|
||||
final Map<String, List<GLAccountData>> accountingMappingOptions, final List<EnumOptionData> accountingRuleOptions,
|
||||
final List<EnumOptionData> valueConditionTypeOptions) {
|
||||
final List<EnumOptionData> valueConditionTypeOptions, final List<EnumOptionData> daysInMonthTypeOptions,
|
||||
final List<EnumOptionData> daysInYearTypeOptions, final List<EnumOptionData> interestRecalculationCompoundingTypeOptions,
|
||||
final List<EnumOptionData> rescheduleStrategyTypeOptions) {
|
||||
this.id = productData.id;
|
||||
this.name = productData.name;
|
||||
this.shortName = productData.shortName;
|
||||
@@ -466,6 +516,16 @@ public class LoanProductData {
|
||||
this.graceOnArrearsAgeing = productData.graceOnArrearsAgeing;
|
||||
this.overdueDaysForNPA = productData.overdueDaysForNPA;
|
||||
|
||||
this.daysInMonthType = productData.daysInMonthType;
|
||||
this.daysInYearType = productData.daysInYearType;
|
||||
this.isInterestRecalculationEnabled = productData.isInterestRecalculationEnabled;
|
||||
this.interestRecalculationData = productData.interestRecalculationData;
|
||||
|
||||
this.daysInMonthTypeOptions = daysInMonthTypeOptions;
|
||||
this.daysInYearTypeOptions = daysInYearTypeOptions;
|
||||
this.interestRecalculationCompoundingTypeOptions = interestRecalculationCompoundingTypeOptions;
|
||||
this.rescheduleStrategyTypeOptions = rescheduleStrategyTypeOptions;
|
||||
|
||||
}
|
||||
|
||||
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
|
||||
@@ -655,6 +715,22 @@ public class LoanProductData {
|
||||
return this.graceOnArrearsAgeing;
|
||||
}
|
||||
|
||||
public EnumOptionData getDaysInMonthType() {
|
||||
return this.daysInMonthType;
|
||||
}
|
||||
|
||||
public EnumOptionData getDaysInYearType() {
|
||||
return this.daysInYearType;
|
||||
}
|
||||
|
||||
public boolean isInterestRecalculationEnabled() {
|
||||
return this.isInterestRecalculationEnabled;
|
||||
}
|
||||
|
||||
public LoanProductInterestRecalculationData getInterestRecalculationData() {
|
||||
return this.interestRecalculationData;
|
||||
}
|
||||
|
||||
public Collection<ChargeData> overdueFeeCharges() {
|
||||
Collection<ChargeData> overdueFeeCharges = new ArrayList<>();
|
||||
Collection<ChargeData> charges = charges();
|
||||
@@ -665,4 +741,22 @@ public class LoanProductData {
|
||||
}
|
||||
return overdueFeeCharges;
|
||||
}
|
||||
|
||||
public LoanInterestRecalculationData toLoanInterestRecalculationData() {
|
||||
final Long id = null;
|
||||
final Long loanId = null;
|
||||
final CalendarData calendarData = null;
|
||||
return new LoanInterestRecalculationData(id, loanId, getInterestRecalculationCompoundingType(), getRescheduleStrategyType(),
|
||||
calendarData);
|
||||
}
|
||||
|
||||
private EnumOptionData getRescheduleStrategyType() {
|
||||
if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRescheduleStrategyType(); }
|
||||
return null;
|
||||
}
|
||||
|
||||
private EnumOptionData getInterestRecalculationCompoundingType() {
|
||||
if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getInterestRecalculationCompoundingType(); }
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package org.mifosplatform.portfolio.loanproduct.data;
|
||||
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationCompoundingType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.data.EnumOptionData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
|
||||
|
||||
public class LoanProductInterestRecalculationData {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final Long id;
|
||||
@SuppressWarnings("unused")
|
||||
private final Long productId;
|
||||
private final EnumOptionData interestRecalculationCompoundingType;
|
||||
private final EnumOptionData rescheduleStrategyType;
|
||||
|
||||
public LoanProductInterestRecalculationData(final Long id, final Long productId,
|
||||
final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType) {
|
||||
this.id = id;
|
||||
this.productId = productId;
|
||||
this.interestRecalculationCompoundingType = interestRecalculationCompoundingType;
|
||||
this.rescheduleStrategyType = rescheduleStrategyType;
|
||||
}
|
||||
|
||||
public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() {
|
||||
final Long id = null;
|
||||
final Long productId = null;
|
||||
final EnumOptionData interestRecalculationCompoundingType = interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.NONE);
|
||||
final EnumOptionData rescheduleStrategyType = rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT);
|
||||
return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType);
|
||||
}
|
||||
|
||||
public EnumOptionData getInterestRecalculationCompoundingType() {
|
||||
return this.interestRecalculationCompoundingType;
|
||||
}
|
||||
|
||||
public EnumOptionData getRescheduleStrategyType() {
|
||||
return this.rescheduleStrategyType;
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package org.mifosplatform.portfolio.loanproduct.domain;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/***
|
||||
* <ul>
|
||||
* People typically use either of the following settings when defining interest
|
||||
* recalculation method:
|
||||
* <li>NONE</li>
|
||||
* <li>INTEREST</li>
|
||||
* <li>FEE</li>
|
||||
* <li>INTEREST_AND_FEE</li>
|
||||
* </ul>
|
||||
*/
|
||||
public enum InterestRecalculationCompoundingMethod {
|
||||
|
||||
NONE(0, "interestRecalculationCompoundingMethod.none"), //
|
||||
INTEREST(1, "interestRecalculationCompoundingMethod.interest"), //
|
||||
FEE(2, "interestRecalculationCompoundingMethod.fee"), //
|
||||
INTEREST_AND_FEE(3, "interestRecalculationCompoundingMethod.interest.and.fee");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
private static final Map<Integer, InterestRecalculationCompoundingMethod> intToEnumMap = new HashMap<>();
|
||||
static {
|
||||
for (final InterestRecalculationCompoundingMethod type : InterestRecalculationCompoundingMethod.values()) {
|
||||
intToEnumMap.put(type.value, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static InterestRecalculationCompoundingMethod fromInt(final Integer ruleTypeValue) {
|
||||
final InterestRecalculationCompoundingMethod type = intToEnumMap.get(ruleTypeValue);
|
||||
return type;
|
||||
}
|
||||
|
||||
private InterestRecalculationCompoundingMethod(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package org.mifosplatform.portfolio.loanproduct.domain;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/***
|
||||
* <ul>
|
||||
* People typically use either of the following settings when defining interest
|
||||
* recalculation method:
|
||||
* <li>SAME_AS_REPAYMENT_PERIOD</li>
|
||||
* <li>DAILY</li>
|
||||
* <li>WEEKLY</li>
|
||||
* <li>FORTNIGHTLY</li>
|
||||
* <li>MONTHLY</li>
|
||||
* </ul>
|
||||
*/
|
||||
|
||||
public enum InterestRecalculationPeriodMethod {
|
||||
INVALID(0, "interestRecalculationPeriodMethod.invalid"), //
|
||||
DAILY(1, "interestRecalculationPeriodMethod.daily"), //
|
||||
WEEKLY(2, "interestRecalculationPeriodMethod.weekly"), //
|
||||
FORTNIGHTLY(3, "interestRecalculationPeriodMethod.fortnightly"), //
|
||||
MONTHLY(4, "interestRecalculationPeriodMethod.monthly"), //
|
||||
SAME_AS_REPAYMENT_PERIOD(5, "interestRecalculationPeriodMethod.same.as.repayment.period");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
private static final Map<Integer, InterestRecalculationPeriodMethod> intToEnumMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (final InterestRecalculationPeriodMethod type : InterestRecalculationPeriodMethod.values()) {
|
||||
intToEnumMap.put(type.value, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static InterestRecalculationPeriodMethod fromInt(final Integer ruleTypeValue) {
|
||||
final InterestRecalculationPeriodMethod type = intToEnumMap.get(ruleTypeValue);
|
||||
return type;
|
||||
}
|
||||
|
||||
private InterestRecalculationPeriodMethod(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
}
|
||||
+74
-3
@@ -24,6 +24,7 @@ import javax.persistence.JoinTable;
|
||||
import javax.persistence.ManyToMany;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
@@ -42,6 +43,7 @@ import org.mifosplatform.portfolio.common.domain.DaysInMonthType;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInYearType;
|
||||
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.fund.domain.Fund;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
@@ -121,6 +123,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
@Column(name = "overdue_days_for_npa", nullable = true)
|
||||
private Integer overdueDaysForNPA;
|
||||
|
||||
@LazyCollection(LazyCollectionOption.FALSE)
|
||||
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true)
|
||||
private LoanProductInterestRecalculationDetails productInterestRecalculationDetails;
|
||||
|
||||
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
|
||||
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
|
||||
|
||||
@@ -187,13 +193,33 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
|
||||
final Integer overdueDaysForNPA = command.integerValueOfParameterNamed(LoanProductConstants.overdueDaysForNPAParameterName);
|
||||
|
||||
// Interest recalculation settings
|
||||
final boolean isInterestRecalculationEnabled = command
|
||||
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName);
|
||||
final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(command
|
||||
.integerValueOfParameterNamed(LoanProductConstants.daysInMonthTypeParameterName));
|
||||
|
||||
final DaysInYearType daysInYearType = DaysInYearType.fromInt(command
|
||||
.integerValueOfParameterNamed(LoanProductConstants.daysInYearTypeParameterName));
|
||||
|
||||
LoanProductInterestRecalculationDetails interestRecalculationSettings = null;
|
||||
|
||||
if (isInterestRecalculationEnabled) {
|
||||
interestRecalculationSettings = LoanProductInterestRecalculationDetails.createFrom(command);
|
||||
}
|
||||
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal,
|
||||
maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType,
|
||||
annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle,
|
||||
startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount,
|
||||
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA);
|
||||
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
|
||||
isInterestRecalculationEnabled, interestRecalculationSettings);
|
||||
|
||||
}
|
||||
|
||||
public void updateLoanProductForInterestRecalculationDetails() {
|
||||
this.productInterestRecalculationDetails.updateProduct(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,7 +430,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle,
|
||||
final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations, final boolean multiDisburseLoan,
|
||||
final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing,
|
||||
final Integer overdueDaysForNPA) {
|
||||
final Integer overdueDaysForNPA, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
|
||||
final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationDetails productInterestRecalculationDetails) {
|
||||
this.fund = fund;
|
||||
this.transactionProcessingStrategy = transactionProcessingStrategy;
|
||||
this.name = name.trim();
|
||||
@@ -422,7 +449,8 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
|
||||
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery,
|
||||
repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing);
|
||||
graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(),
|
||||
daysInYearType.getValue(), isInterestRecalculationEnabled);
|
||||
|
||||
this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
|
||||
|
||||
@@ -451,6 +479,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
}
|
||||
this.loanProducTrancheDetails = new LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount, outstandingLoanBalance);
|
||||
this.overdueDaysForNPA = overdueDaysForNPA;
|
||||
this.productInterestRecalculationDetails = productInterestRecalculationDetails;
|
||||
}
|
||||
|
||||
public MonetaryCurrency getCurrency() {
|
||||
@@ -622,6 +651,27 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
this.overdueDaysForNPA = newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update interest recalculation settings
|
||||
*/
|
||||
final boolean isInterestRecalculationEnabledChanged = actualChanges
|
||||
.containsKey(LoanProductConstants.isInterestRecalculationEnabledParameterName);
|
||||
|
||||
if (isInterestRecalculationEnabledChanged) {
|
||||
if (this.isInterestRecalculationEnabled()) {
|
||||
this.productInterestRecalculationDetails = LoanProductInterestRecalculationDetails.createFrom(command);
|
||||
this.productInterestRecalculationDetails.updateProduct(this);
|
||||
actualChanges.put(LoanProductConstants.interestRecalculationCompoundingMethodParameterName,
|
||||
command.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName));
|
||||
actualChanges.put(LoanProductConstants.rescheduleStrategyMethodParameterName,
|
||||
command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName));
|
||||
} else {
|
||||
this.productInterestRecalculationDetails = null;
|
||||
}
|
||||
} else {
|
||||
this.productInterestRecalculationDetails.update(command, actualChanges, localeAsInput);
|
||||
}
|
||||
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
@@ -738,6 +788,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
return this.loanProducTrancheDetails.maxTrancheCount();
|
||||
}
|
||||
|
||||
public boolean isInterestRecalculationEnabled() {
|
||||
return this.loanProductRelatedDetail.isInterestRecalculationEnabled();
|
||||
}
|
||||
|
||||
public LoanProductBorrowerCycleVariations fetchLoanProductBorrowerCycleVariationById(Long id) {
|
||||
LoanProductBorrowerCycleVariations borrowerCycleVariation = null;
|
||||
for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) {
|
||||
@@ -850,4 +904,21 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
return this.loanProductRelatedDetail.fetchDaysInYearType();
|
||||
}
|
||||
|
||||
public LoanInterestRecalculationDetails copyInterestRecalculationSettings() {
|
||||
LoanInterestRecalculationDetails loanInterestRecalculationDetails = null;
|
||||
if (this.isInterestRecalculationEnabled()) {
|
||||
loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(
|
||||
this.productInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(),
|
||||
this.productInterestRecalculationDetails.getRescheduleStrategyMethod());
|
||||
}
|
||||
return loanInterestRecalculationDetails;
|
||||
}
|
||||
|
||||
public LoanInterestRecalculationDetails updateLoanInterestRecalculationDetails(
|
||||
LoanInterestRecalculationDetails loanInterestRecalculationDetails) {
|
||||
|
||||
loanInterestRecalculationDetails.update(this.productInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(),
|
||||
this.productInterestRecalculationDetails.getRescheduleStrategyMethod());
|
||||
return loanInterestRecalculationDetails;
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package org.mifosplatform.portfolio.loanproduct.domain;
|
||||
|
||||
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 for capturing interest recalculation settings
|
||||
*
|
||||
* @author conflux
|
||||
*/
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_product_loan_recalculation_details")
|
||||
public class LoanProductInterestRecalculationDetails extends AbstractPersistable<Long> {
|
||||
|
||||
@OneToOne
|
||||
@JoinColumn(name = "product_id", nullable = false)
|
||||
private LoanProduct loanProduct;
|
||||
|
||||
/**
|
||||
* {@link InterestRecalculationCompoundingMethod}
|
||||
*/
|
||||
@Column(name = "compound_type_enum", nullable = false)
|
||||
private Integer interestRecalculationCompoundingMethod;
|
||||
|
||||
/**
|
||||
* {@link LoanRescheduleStrategyMethod}
|
||||
*/
|
||||
@Column(name = "reschedule_strategy_enum", nullable = false)
|
||||
private Integer rescheduleStrategyMethod;
|
||||
|
||||
protected LoanProductInterestRecalculationDetails() {
|
||||
//
|
||||
}
|
||||
|
||||
public static LoanProductInterestRecalculationDetails createFrom(final JsonCommand command) {
|
||||
|
||||
final Integer interestRecalculationCompoundingMethod = InterestRecalculationCompoundingMethod.fromInt(
|
||||
command.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName)).getValue();
|
||||
|
||||
final Integer loanRescheduleStrategyMethod = LoanRescheduleStrategyMethod.fromInt(
|
||||
command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName)).getValue();
|
||||
|
||||
return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod);
|
||||
}
|
||||
|
||||
private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod,
|
||||
final Integer rescheduleStrategyMethod) {
|
||||
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
|
||||
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
|
||||
}
|
||||
|
||||
public void updateProduct(final LoanProduct loanProduct) {
|
||||
this.loanProduct = loanProduct;
|
||||
}
|
||||
|
||||
public Integer getInterestRecalculationCompoundingMethod() {
|
||||
return this.interestRecalculationCompoundingMethod;
|
||||
}
|
||||
|
||||
public Integer getRescheduleStrategyMethod() {
|
||||
return this.rescheduleStrategyMethod;
|
||||
}
|
||||
|
||||
public void update(final JsonCommand command, final Map<String, Object> actualChanges, final String localeAsInput) {
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName,
|
||||
this.interestRecalculationCompoundingMethod)) {
|
||||
final Integer newValue = command
|
||||
.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName);
|
||||
actualChanges.put(LoanProductConstants.interestRecalculationCompoundingMethodParameterName, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.interestRecalculationCompoundingMethod = InterestRecalculationCompoundingMethod.fromInt(newValue).getValue();
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName,
|
||||
this.rescheduleStrategyMethod)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName);
|
||||
actualChanges.put(LoanProductConstants.rescheduleStrategyMethodParameterName, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.rescheduleStrategyMethod = LoanRescheduleStrategyMethod.fromInt(newValue).getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
-5
@@ -100,18 +100,28 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
@Column(name = "grace_on_arrears_ageing", nullable = true)
|
||||
private Integer graceOnArrearsAgeing;
|
||||
|
||||
@Column(name = "days_in_month_enum", nullable = false)
|
||||
private Integer daysInMonthType;
|
||||
|
||||
@Column(name = "days_in_year_enum", nullable = false)
|
||||
private Integer daysInYearType;
|
||||
|
||||
@Column(name = "interest_recalculation_enabled")
|
||||
private boolean isInterestRecalculationEnabled;
|
||||
|
||||
public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal,
|
||||
final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType,
|
||||
final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod,
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repaymentEvery,
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments,
|
||||
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
|
||||
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing) {
|
||||
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing,
|
||||
final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) {
|
||||
|
||||
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
|
||||
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType,
|
||||
numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
||||
inArrearsTolerance, graceOnArrearsAgeing);
|
||||
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled);
|
||||
}
|
||||
|
||||
protected LoanProductRelatedDetail() {
|
||||
@@ -124,7 +134,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery,
|
||||
final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments,
|
||||
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
|
||||
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing) {
|
||||
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing,
|
||||
final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) {
|
||||
this.currency = currency;
|
||||
this.principal = defaultPrincipal;
|
||||
this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod;
|
||||
@@ -145,6 +156,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
this.inArrearsTolerance = inArrearsTolerance;
|
||||
}
|
||||
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
|
||||
this.daysInMonthType = daysInMonthType;
|
||||
this.daysInYearType = daysInYearType;
|
||||
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
|
||||
}
|
||||
|
||||
private Integer defaultToNullIfZero(final Integer value) {
|
||||
@@ -383,6 +397,28 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
this.graceOnArrearsAgeing = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.daysInMonthTypeParameterName, this.daysInMonthType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.daysInMonthTypeParameterName);
|
||||
actualChanges.put(LoanProductConstants.daysInMonthTypeParameterName, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.daysInMonthType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.daysInYearTypeParameterName, this.daysInYearType)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.daysInYearTypeParameterName);
|
||||
actualChanges.put(LoanProductConstants.daysInYearTypeParameterName, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.daysInYearType = newValue;
|
||||
}
|
||||
|
||||
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName,
|
||||
this.isInterestRecalculationEnabled)) {
|
||||
final boolean newValue = command
|
||||
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName);
|
||||
actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, newValue);
|
||||
this.isInterestRecalculationEnabled = newValue;
|
||||
}
|
||||
|
||||
validateRepaymentPeriodWithGraceSettings();
|
||||
|
||||
return actualChanges;
|
||||
@@ -435,10 +471,18 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
}
|
||||
|
||||
public DaysInMonthType fetchDaysInMonthType() {
|
||||
return DaysInMonthType.ACTUAL;
|
||||
return DaysInMonthType.fromInt(this.daysInMonthType);
|
||||
}
|
||||
|
||||
public DaysInYearType fetchDaysInYearType() {
|
||||
return DaysInYearType.ACTUAL;
|
||||
return DaysInYearType.fromInt(this.daysInYearType);
|
||||
}
|
||||
|
||||
public boolean isInterestRecalculationEnabled() {
|
||||
return this.isInterestRecalculationEnabled;
|
||||
}
|
||||
|
||||
public void updateIsInterestRecalculationEnabled(final boolean isInterestRecalculationEnabled) {
|
||||
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled;
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package org.mifosplatform.portfolio.loanproduct.domain;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/***
|
||||
* <ul>
|
||||
* People typically use either of the following settings when defining interest
|
||||
* recalculation method:
|
||||
* <li>RESCHEDULE_NEXT_REPAYMENTS</li>
|
||||
* <li>REDUCE_NUMBER_OF_INSTALLMENTS</li>
|
||||
* <li>REDUCE_EMI_AMOUNT</li>
|
||||
* </ul>
|
||||
*/
|
||||
|
||||
public enum LoanRescheduleStrategyMethod {
|
||||
|
||||
INVALID(0, "loanRescheduleStrategyMethod.invalid"), //
|
||||
RESCHEDULE_NEXT_REPAYMENTS(1, "loanRescheduleStrategyMethod.reschedule.next.repayments"), //
|
||||
REDUCE_NUMBER_OF_INSTALLMENTS(2, "loanRescheduleStrategyMethod.reduce.number.of.installments"), //
|
||||
REDUCE_EMI_AMOUNT(3, "loanRescheduleStrategyMethod.reduce.emi.amount");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
private static final Map<Integer, LoanRescheduleStrategyMethod> intToEnumMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (final LoanRescheduleStrategyMethod type : LoanRescheduleStrategyMethod.values()) {
|
||||
intToEnumMap.put(type.value, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static LoanRescheduleStrategyMethod fromInt(final Integer ruleTypeValue) {
|
||||
final LoanRescheduleStrategyMethod type = intToEnumMap.get(ruleTypeValue);
|
||||
return type;
|
||||
}
|
||||
|
||||
private LoanRescheduleStrategyMethod(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package org.mifosplatform.portfolio.loanproduct.domain;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum RecalculationFrequencyType {
|
||||
INVALID(0, "interestRecalculationFrequencyType.invalid"), //
|
||||
SAME_AS_REPAYMENT_PERIOD(1, "interestRecalculationFrequencyType.same.as.repayment.period"), //
|
||||
DAILY(2, "interestRecalculationFrequencyType.daily"), //
|
||||
WEEKLY(3, "interestRecalculationFrequencyType.weekly"), //
|
||||
FORTNIGHTLY(4, "interestRecalculationFrequencyType.fortnightly"), //
|
||||
MONTHLY(5, "interestRecalculationFrequencyType.monthly");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
private static final Map<Integer, RecalculationFrequencyType> intToEnumMap = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (final RecalculationFrequencyType type : RecalculationFrequencyType.values()) {
|
||||
intToEnumMap.put(type.value, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static RecalculationFrequencyType fromInt(final Integer ruleTypeValue) {
|
||||
if (ruleTypeValue == null) { return RecalculationFrequencyType.INVALID; }
|
||||
final RecalculationFrequencyType type = intToEnumMap.get(ruleTypeValue);
|
||||
return type;
|
||||
}
|
||||
|
||||
private RecalculationFrequencyType(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
}
|
||||
+86
-1
@@ -61,7 +61,10 @@ public final class LoanProductDataValidator {
|
||||
LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName, LoanProductConstants.shortName,
|
||||
LoanProductConstants.multiDisburseLoanParameterName, LoanProductConstants.outstandingLoanBalanceParameterName,
|
||||
LoanProductConstants.maxTrancheCountParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName,
|
||||
LoanProductConstants.overdueDaysForNPAParameterName));
|
||||
LoanProductConstants.overdueDaysForNPAParameterName, LoanProductConstants.isInterestRecalculationEnabledParameterName,
|
||||
LoanProductConstants.daysInYearTypeParameterName, LoanProductConstants.daysInMonthTypeParameterName,
|
||||
LoanProductConstants.rescheduleStrategyMethodParameterName,
|
||||
LoanProductConstants.interestRecalculationCompoundingMethodParameterName));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@@ -264,6 +267,34 @@ public final class LoanProductDataValidator {
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.overdueDaysForNPAParameterName).value(overdueDaysForNPA)
|
||||
.integerZeroOrGreater();
|
||||
|
||||
/**
|
||||
* { @link DaysInYearType }
|
||||
*/
|
||||
final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInYearTypeParameterName,
|
||||
element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.daysInYearTypeParameterName).value(daysInYearType).notNull()
|
||||
.isOneOfTheseValues(1, 360, 364, 365);
|
||||
|
||||
/**
|
||||
* { @link DaysInMonthType }
|
||||
*/
|
||||
final Integer daysInMonthType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInMonthTypeParameterName,
|
||||
element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull()
|
||||
.isOneOfTheseValues(1, 30);
|
||||
|
||||
// Interest recalculation settings
|
||||
final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
LoanProductConstants.isInterestRecalculationEnabledParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName)
|
||||
.value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false);
|
||||
|
||||
if (isInterestRecalculationEnabled != null) {
|
||||
if (isInterestRecalculationEnabled.booleanValue()) {
|
||||
validateInterestRecalculationParams(element, baseDataValidator);
|
||||
}
|
||||
}
|
||||
|
||||
// accounting related data validation
|
||||
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
|
||||
@@ -380,6 +411,26 @@ public final class LoanProductDataValidator {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateInterestRecalculationParams(final JsonElement element, final DataValidatorBuilder baseDataValidator) {
|
||||
|
||||
/**
|
||||
* { @link InterestRecalculationCompoundingMethod }
|
||||
*/
|
||||
final Integer interestRecalculationCompoundingMethod = this.fromApiJsonHelper.extractIntegerNamed(
|
||||
LoanProductConstants.interestRecalculationCompoundingMethodParameterName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.interestRecalculationCompoundingMethodParameterName)
|
||||
.value(interestRecalculationCompoundingMethod).notNull().inMinMaxRange(0, 3);
|
||||
|
||||
/**
|
||||
* { @link LoanRescheduleStrategyMethod }
|
||||
*/
|
||||
final Integer rescheduleStrategyMethod = this.fromApiJsonHelper.extractIntegerNamed(
|
||||
LoanProductConstants.rescheduleStrategyMethodParameterName, element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).value(rescheduleStrategyMethod)
|
||||
.notNull().inMinMaxRange(1, 3);
|
||||
|
||||
}
|
||||
|
||||
public void validateForUpdate(final String json, final LoanProduct loanProduct) {
|
||||
if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
|
||||
|
||||
@@ -574,6 +625,40 @@ public final class LoanProductDataValidator {
|
||||
.inMinMaxRange(0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* { @link DaysInYearType }
|
||||
*/
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.daysInYearTypeParameterName, element)) {
|
||||
final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInYearTypeParameterName,
|
||||
element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.daysInYearTypeParameterName).value(daysInYearType).notNull()
|
||||
.isOneOfTheseValues(1, 360, 364, 365);
|
||||
}
|
||||
|
||||
/**
|
||||
* { @link DaysInMonthType }
|
||||
*/
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.daysInMonthTypeParameterName, element)) {
|
||||
final Integer daysInMonthType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInMonthTypeParameterName,
|
||||
element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull()
|
||||
.isOneOfTheseValues(1, 30);
|
||||
}
|
||||
|
||||
// Interest recalculation settings
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) {
|
||||
final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed(
|
||||
LoanProductConstants.isInterestRecalculationEnabledParameterName, element);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName)
|
||||
.value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false);
|
||||
|
||||
if (isInterestRecalculationEnabled != null) {
|
||||
if (isInterestRecalculationEnabled.booleanValue()) {
|
||||
validateInterestRecalculationParams(element, baseDataValidator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).ignoreIfNull().inMinMaxRange(1, 4);
|
||||
|
||||
|
||||
+6
@@ -29,4 +29,10 @@ public interface LoanDropdownReadPlatformService {
|
||||
|
||||
List<EnumOptionData> retrieveLoanCycleValueConditionTypeOptions();
|
||||
|
||||
List<EnumOptionData> retrieveInterestRecalculationCompoundingTypeOptions();
|
||||
|
||||
List<EnumOptionData> retrieveRescheduleStrategyTypeOptions();
|
||||
|
||||
List<EnumOptionData> retrieveInterestRecalculationFrequencyTypeOptions();
|
||||
|
||||
}
|
||||
+39
@@ -8,10 +8,13 @@ package org.mifosplatform.portfolio.loanproduct.service;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.amortizationType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestCalculationPeriodType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRateFrequencyType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationCompoundingType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.loanCycleValueConditionType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.loanTermFrequencyType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType;
|
||||
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationFrequencyType;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -25,8 +28,11 @@ import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrateg
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -112,4 +118,37 @@ public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlat
|
||||
return repaymentFrequencyOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnumOptionData> retrieveInterestRecalculationCompoundingTypeOptions() {
|
||||
|
||||
final List<EnumOptionData> interestRecalculationCompoundingTypeOptions = Arrays.asList(
|
||||
interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.NONE),
|
||||
interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.FEE),
|
||||
interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.INTEREST),
|
||||
interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE));
|
||||
return interestRecalculationCompoundingTypeOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnumOptionData> retrieveRescheduleStrategyTypeOptions() {
|
||||
|
||||
final List<EnumOptionData> rescheduleStrategyTypeOptions = Arrays.asList(
|
||||
rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT),
|
||||
rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS),
|
||||
rescheduleStrategyType(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS));
|
||||
return rescheduleStrategyTypeOptions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<EnumOptionData> retrieveInterestRecalculationFrequencyTypeOptions() {
|
||||
|
||||
final List<EnumOptionData> interestRecalculationFrequencyTypeOptions = Arrays.asList(
|
||||
interestRecalculationFrequencyType(RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD),
|
||||
interestRecalculationFrequencyType(RecalculationFrequencyType.DAILY),
|
||||
interestRecalculationFrequencyType(RecalculationFrequencyType.WEEKLY),
|
||||
interestRecalculationFrequencyType(RecalculationFrequencyType.FORTNIGHTLY),
|
||||
interestRecalculationFrequencyType(RecalculationFrequencyType.MONTHLY));
|
||||
return interestRecalculationFrequencyTypeOptions;
|
||||
}
|
||||
|
||||
}
|
||||
+101
-3
@@ -12,13 +12,16 @@ import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType;
|
||||
|
||||
public class LoanEnumerations {
|
||||
|
||||
@@ -32,6 +35,8 @@ public class LoanEnumerations {
|
||||
public static final String PAYMENT_TYPE = "paymentType";
|
||||
public static final String ACCOUNTING_RULE_TYPE = "accountingRule";
|
||||
public static final String LOAN_TYPE = "loanType";
|
||||
public static final String INTEREST_RECALCULATION_COMPOUNDING_TYPE = "interestRecalculationCompoundingType";
|
||||
public static final String RESCHEDULE_STRATEGY_TYPE = "rescheduleStrategyType";
|
||||
|
||||
public static EnumOptionData loanEnumueration(final String typeName, final int id) {
|
||||
if (typeName.equals(LOAN_TERM_FREQUENCY_TYPE)) {
|
||||
@@ -50,8 +55,12 @@ public class LoanEnumerations {
|
||||
return interestCalculationPeriodType(id);
|
||||
} else if (typeName.equals(ACCOUNTING_RULE_TYPE)) {
|
||||
return AccountingEnumerations.accountingRuleType(id);
|
||||
} else if (typeName.equals(LOAN_TYPE)) {
|
||||
return AccountEnumerations.loanType(id);
|
||||
} else if (typeName.equals(LOAN_TYPE)) {
|
||||
return AccountEnumerations.loanType(id);
|
||||
} else if (typeName.equals(INTEREST_RECALCULATION_COMPOUNDING_TYPE)) {
|
||||
return interestRecalculationCompoundingType(id);
|
||||
} else if (typeName.equals(RESCHEDULE_STRATEGY_TYPE)) {
|
||||
return rescheduleStrategyType(id);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -452,4 +461,93 @@ public class LoanEnumerations {
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData interestRecalculationCompoundingType(final int id) {
|
||||
return interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.fromInt(id));
|
||||
}
|
||||
|
||||
public static EnumOptionData interestRecalculationCompoundingType(final InterestRecalculationCompoundingMethod type) {
|
||||
EnumOptionData optionData = null;
|
||||
switch (type) {
|
||||
case FEE:
|
||||
optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.FEE.getValue().longValue(),
|
||||
InterestRecalculationCompoundingMethod.FEE.getCode(), "Fee");
|
||||
break;
|
||||
case INTEREST:
|
||||
optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.INTEREST.getValue().longValue(),
|
||||
InterestRecalculationCompoundingMethod.INTEREST.getCode(), "Interest");
|
||||
break;
|
||||
case INTEREST_AND_FEE:
|
||||
optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue().longValue(),
|
||||
InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getCode(), "Fee and Interest");
|
||||
break;
|
||||
default:
|
||||
optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.NONE.getValue().longValue(),
|
||||
InterestRecalculationCompoundingMethod.NONE.getCode(), "None");
|
||||
break;
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData rescheduleStrategyType(final int id) {
|
||||
return rescheduleStrategyType(LoanRescheduleStrategyMethod.fromInt(id));
|
||||
}
|
||||
|
||||
public static EnumOptionData rescheduleStrategyType(final LoanRescheduleStrategyMethod type) {
|
||||
EnumOptionData optionData = null;
|
||||
switch (type) {
|
||||
case REDUCE_EMI_AMOUNT:
|
||||
optionData = new EnumOptionData(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT.getValue().longValue(),
|
||||
LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT.getCode(), "Reduce EMI amount");
|
||||
break;
|
||||
case REDUCE_NUMBER_OF_INSTALLMENTS:
|
||||
optionData = new EnumOptionData(LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS.getValue().longValue(),
|
||||
LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS.getCode(), "Reduce number of installments");
|
||||
break;
|
||||
case RESCHEDULE_NEXT_REPAYMENTS:
|
||||
optionData = new EnumOptionData(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS.getValue().longValue(),
|
||||
LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS.getCode(), "Reschedule next repayments");
|
||||
break;
|
||||
default:
|
||||
optionData = new EnumOptionData(LoanRescheduleStrategyMethod.INVALID.getValue().longValue(),
|
||||
LoanRescheduleStrategyMethod.INVALID.getCode(), "Invalid");
|
||||
break;
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
|
||||
public static EnumOptionData interestRecalculationFrequencyType(final int id) {
|
||||
return interestRecalculationFrequencyType(RecalculationFrequencyType.fromInt(id));
|
||||
}
|
||||
|
||||
public static EnumOptionData interestRecalculationFrequencyType(final RecalculationFrequencyType type) {
|
||||
EnumOptionData optionData = null;
|
||||
switch (type) {
|
||||
case DAILY:
|
||||
optionData = new EnumOptionData(RecalculationFrequencyType.DAILY.getValue().longValue(),
|
||||
RecalculationFrequencyType.DAILY.getCode(), "Daily");
|
||||
break;
|
||||
case FORTNIGHTLY:
|
||||
optionData = new EnumOptionData(RecalculationFrequencyType.FORTNIGHTLY.getValue().longValue(),
|
||||
RecalculationFrequencyType.FORTNIGHTLY.getCode(), "Fortnightly");
|
||||
break;
|
||||
case MONTHLY:
|
||||
optionData = new EnumOptionData(RecalculationFrequencyType.MONTHLY.getValue().longValue(),
|
||||
RecalculationFrequencyType.MONTHLY.getCode(), "Monthly");
|
||||
break;
|
||||
case SAME_AS_REPAYMENT_PERIOD:
|
||||
optionData = new EnumOptionData(RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD.getValue().longValue(),
|
||||
RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD.getCode(), "Same as repayment period");
|
||||
break;
|
||||
case WEEKLY:
|
||||
optionData = new EnumOptionData(RecalculationFrequencyType.WEEKLY.getValue().longValue(),
|
||||
RecalculationFrequencyType.WEEKLY.getCode(), "Weekly");
|
||||
break;
|
||||
default:
|
||||
optionData = new EnumOptionData(RecalculationFrequencyType.INVALID.getValue().longValue(),
|
||||
RecalculationFrequencyType.INVALID.getCode(), "Invalid");
|
||||
break;
|
||||
|
||||
}
|
||||
return optionData;
|
||||
}
|
||||
}
|
||||
+30
-3
@@ -21,8 +21,10 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.portfolio.charge.data.ChargeData;
|
||||
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
|
||||
import org.mifosplatform.portfolio.common.service.CommonEnumerations;
|
||||
import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
|
||||
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
|
||||
import org.mifosplatform.portfolio.loanproduct.data.LoanProductInterestRecalculationData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType;
|
||||
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -121,9 +123,12 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
+ "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, "
|
||||
+ "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, "
|
||||
+ "lp.allow_multiple_disbursals as multiDisburseLoan, lp.max_disbursals as maxTrancheCount, lp.max_outstanding_loan_balance as outstandingLoanBalance, "
|
||||
+ "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, "
|
||||
+ "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, "
|
||||
+ "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId "
|
||||
+ " from m_product_loan lp "
|
||||
+ " left join m_fund f on f.id = lp.fund_id"
|
||||
+ " 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 "
|
||||
+ " left join ref_loan_transaction_processing_strategy ltps on ltps.id = lp.loan_transaction_strategy_id"
|
||||
+ " join m_currency curr on curr.code = lp.currency_code";
|
||||
}
|
||||
@@ -131,7 +136,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
@Override
|
||||
public LoanProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final Long id = JdbcSupport.getLong(rs, "id");
|
||||
final String name = rs.getString("name");
|
||||
final String shortName = rs.getString("shortName");
|
||||
final String description = rs.getString("description");
|
||||
@@ -221,6 +226,27 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
final Integer maxTrancheCount = rs.getInt("maxTrancheCount");
|
||||
final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance");
|
||||
|
||||
final int daysInMonth = JdbcSupport.getInteger(rs, "daysInMonth");
|
||||
final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(daysInMonth);
|
||||
final int daysInYear = JdbcSupport.getInteger(rs, "daysInYear");
|
||||
final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(daysInYear);
|
||||
final boolean isInterestRecalculationEnabled = rs.getBoolean("isInterestRecalculationEnabled");
|
||||
|
||||
LoanProductInterestRecalculationData interestRecalculationData = null;
|
||||
if (isInterestRecalculationEnabled) {
|
||||
|
||||
final Long lprId = JdbcSupport.getLong(rs, "lprId");
|
||||
final Long productId = JdbcSupport.getLong(rs, "productId");
|
||||
final int compoundTypeEnumValue = JdbcSupport.getInteger(rs, "compoundType");
|
||||
final EnumOptionData interestRecalculationCompoundingType = LoanEnumerations
|
||||
.interestRecalculationCompoundingType(compoundTypeEnumValue);
|
||||
final int rescheduleStrategyEnumValue = JdbcSupport.getInteger(rs, "rescheduleStrategy");
|
||||
final EnumOptionData rescheduleStrategyType = LoanEnumerations.rescheduleStrategyType(rescheduleStrategyEnumValue);
|
||||
|
||||
interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId,
|
||||
interestRecalculationCompoundingType, rescheduleStrategyType);
|
||||
}
|
||||
|
||||
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
|
||||
@@ -229,7 +255,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, useBorrowerCycle, startDate,
|
||||
closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle,
|
||||
numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance,
|
||||
graceOnArrearsAgeing, overdueDaysForNPA);
|
||||
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
|
||||
interestRecalculationData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -92,6 +92,9 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
|
||||
|
||||
final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command,
|
||||
this.aprCalculator);
|
||||
if (loanproduct.isInterestRecalculationEnabled()) {
|
||||
loanproduct.updateLoanProductForInterestRecalculationDetails();
|
||||
}
|
||||
|
||||
this.loanProductRepository.save(loanproduct);
|
||||
|
||||
|
||||
+7
-1
@@ -3,6 +3,8 @@ package org.mifosplatform.portfolio.loanaccount;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInMonthType;
|
||||
import org.mifosplatform.portfolio.common.domain.DaysInYearType;
|
||||
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
@@ -128,9 +130,13 @@ public class LoanProductRelatedDetailTestHelper {
|
||||
final Integer graceOnInterestCharged = Integer.valueOf(0);
|
||||
final Integer graceOnArrearsAgeing = Integer.valueOf(0);
|
||||
|
||||
final Integer daysInMonthType = DaysInMonthType.ACTUAL.getValue();
|
||||
final Integer daysInYearType = DaysInYearType.ACTUAL.getValue();
|
||||
final boolean isInterestRecalculationEnabled = false;
|
||||
|
||||
return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
|
||||
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
|
||||
defaultNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
||||
inArrearsTolerance, graceOnArrearsAgeing);
|
||||
inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user