Merge pull request #1033 from lingalamadhukar/MIFOSX-1420

MIFOSX-1419,MIFOSX-1420 Interest recalculation crud operations
This commit is contained in:
Vishwas Babu A J
2014-07-30 17:35:42 -07:00
40 changed files with 1808 additions and 76 deletions
+269 -6
View File
@@ -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;
@@ -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);
}
@@ -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);
}
@@ -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();
}
}
@@ -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;
@@ -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;
}
}
@@ -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();
}
@@ -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;
}
}
@@ -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";
}
@@ -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);
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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() {
@@ -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) {
@@ -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;
@@ -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); }
}
}
@@ -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()) //
@@ -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
@@ -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;
}
@@ -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";
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
@@ -29,4 +29,10 @@ public interface LoanDropdownReadPlatformService {
List<EnumOptionData> retrieveLoanCycleValueConditionTypeOptions();
List<EnumOptionData> retrieveInterestRecalculationCompoundingTypeOptions();
List<EnumOptionData> retrieveRescheduleStrategyTypeOptions();
List<EnumOptionData> retrieveInterestRecalculationFrequencyTypeOptions();
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
@@ -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);
}
}