MIFOSX-295: support principal and interest grace along with ability to have interest-free periods.
This commit is contained in:
+63
-3
@@ -11,7 +11,7 @@
|
||||
var result = {};
|
||||
if (window.location.search) {
|
||||
// split up the query string and store in an associative array
|
||||
var params = window.location.search.slice(1).split("&"); x
|
||||
var params = window.location.search.slice(1).split("&");
|
||||
for ( var i = 0; i < params.length; i++) {
|
||||
var tmp = params[i].split("=");
|
||||
result[tmp[0]] = unescape(tmp[1]);
|
||||
@@ -3821,6 +3821,34 @@ GET https://DomainName/mifosng-provider/api/v1/groups
|
||||
<span>Example Values:</span> 2=Per month, 3=Per year
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnPrincipalPayment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the principal component of a repayment period.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnInterestPayment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the interest component of a repayment period. Interest is still calculated but offset to later repayment periods.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnInterestCharged</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that should be interest-free.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>interestChargedFromDate</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Date - The date from with interest is to start being charged.</td>
|
||||
</tr>
|
||||
|
||||
<tr class=alt>
|
||||
<td>expectedDisbursementDate</td>
|
||||
</tr>
|
||||
@@ -4849,6 +4877,15 @@ Request Body:
|
||||
expectedDisbursementDate, submittedOnDate, loanType</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br/>
|
||||
<table class=matrixHeading>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Optional Fields</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="method-example">
|
||||
<code class="method-declaration">
|
||||
@@ -6967,6 +7004,28 @@ Request Body:
|
||||
</ol>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnPrincipalPayment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the principal component of a repayment period.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnInterestPayment</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that grace should apply to the interest component of a repayment period. Interest is still calculated but offset to later repayment periods.
|
||||
</td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>graceOnInterestCharged</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class=fielddesc>Optional: Integer - represents the number of repayment periods that should be interest-free.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class=alt>
|
||||
<td>accountingRule</td>
|
||||
</tr>
|
||||
@@ -7524,13 +7583,14 @@ GET https://DomainName/api/v1/loanproducts/{productId}
|
||||
<td>name, currencyCode, digitsAfterDecimal, principal,
|
||||
numberOfRepayments, repaymentEvery, repaymentFrequencyType,
|
||||
interestRatePerPeriod, interestRateFrequencyType, amortizationType, interestType,
|
||||
interestCalculationPeriodType, transactionProcessingStrategyId, accountingRule</td>
|
||||
interestCalculationPeriodType, transactionProcessingStrategyId,
|
||||
accountingRule</td>
|
||||
</tr>
|
||||
<tr class="matrixHeadingBG">
|
||||
<td><div class="mifosXHeading2">Optional Fields</div></td>
|
||||
</tr>
|
||||
<tr class=alt>
|
||||
<td>inArrearsTolerance, charges,paymentChannelToFundSourceMappings</td>
|
||||
<td>inArrearsTolerance, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, paymentChannelToFundSourceMappings</td>
|
||||
</tr>
|
||||
|
||||
<tr class="matrixHeadingBG">
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
ALTER TABLE `m_product_loan`
|
||||
ADD COLUMN `grace_on_principal_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `max_number_of_repayments`,
|
||||
ADD COLUMN `grace_on_interest_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_principal_periods`,
|
||||
ADD COLUMN `grace_interest_free_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_interest_periods`;
|
||||
|
||||
ALTER TABLE `m_loan`
|
||||
ADD COLUMN `grace_on_principal_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `number_of_repayments`,
|
||||
ADD COLUMN `grace_on_interest_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_principal_periods`,
|
||||
ADD COLUMN `grace_interest_free_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_interest_periods`;
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.accounting.glaccount.data;
|
||||
|
||||
public class GLAccountDataForLookup {
|
||||
|
||||
+13
@@ -47,6 +47,19 @@ public class JdbcSupport {
|
||||
return (Integer) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Integer.class);
|
||||
}
|
||||
|
||||
public static Integer getIntegerDefaultToNullIfZero(final ResultSet rs, final String columnName) throws SQLException {
|
||||
Integer value = (Integer) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Integer.class);
|
||||
return defaultToNullIfZero(value);
|
||||
}
|
||||
|
||||
private static Integer defaultToNullIfZero(final Integer value) {
|
||||
Integer result = value;
|
||||
if (result != null && Integer.valueOf(0).equals(value)) {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BigDecimal getBigDecimalDefaultToZeroIfNull(final ResultSet rs, final String columnName) throws SQLException {
|
||||
final BigDecimal value = rs.getBigDecimal(columnName);
|
||||
return defaultToZeroIfNull(value);
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.savings.domain;
|
||||
package org.mifosplatform.infrastructure.core.domain;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
+4
@@ -77,6 +77,10 @@ public class ApplicationCurrency extends AbstractPersistable<Long> {
|
||||
return new CurrencyData(this.code, this.name, this.decimalPlaces, this.displaySymbol, this.nameCode);
|
||||
}
|
||||
|
||||
public CurrencyData toData(final int digitsAfterDecimalSupported) {
|
||||
return new CurrencyData(this.code, this.name, digitsAfterDecimalSupported, this.displaySymbol, this.nameCode);
|
||||
}
|
||||
|
||||
public OrganisationCurrency toOrganisationCurrency() {
|
||||
return new OrganisationCurrency(this.code, this.name, this.decimalPlaces, this.nameCode, this.displaySymbol);
|
||||
}
|
||||
|
||||
+4
@@ -256,4 +256,8 @@ public class Money implements Comparable<Money> {
|
||||
private MonetaryCurrency monetaryCurrency() {
|
||||
return new MonetaryCurrency(this.currencyCode, this.currencyDigitsAfterDecimal);
|
||||
}
|
||||
|
||||
public Money zero() {
|
||||
return Money.zero(getCurrency());
|
||||
}
|
||||
}
|
||||
+31
-29
@@ -64,6 +64,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.NotSupportedLoanTemplat
|
||||
import org.mifosplatform.portfolio.loanaccount.guarantor.data.GuarantorData;
|
||||
import org.mifosplatform.portfolio.loanaccount.guarantor.service.GuarantorReadPlatformService;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
|
||||
import org.mifosplatform.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||
import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
@@ -88,12 +89,12 @@ public class LoansApiResource {
|
||||
"loanPurposeName", "loanOfficerId", "loanOfficerName", "currency", "principal", "inArrearsTolerance", "termFrequency",
|
||||
"termPeriodFrequencyType", "numberOfRepayments", "repaymentEvery", "interestRatePerPeriod", "annualInterestRate",
|
||||
"repaymentFrequencyType", "transactionProcessingStrategyId", "interestRateFrequencyType", "amortizationType", "interestType",
|
||||
"interestCalculationPeriodType", "expectedFirstRepaymentOnDate", "interestChargedFromDate", "timeline",
|
||||
"totalFeeChargesAtDisbursement", "summary", "repaymentSchedule", "transactions", "charges", "collateral", "guarantors","meeting",
|
||||
"productOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions",
|
||||
"repaymentFrequencyTypeOptions", "termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions",
|
||||
"repaymentStrategyOptions", "chargeOptions", "loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions",
|
||||
"chargeTemplate","calendarOptions"));
|
||||
"interestCalculationPeriodType", "expectedFirstRepaymentOnDate", "graceOnPrincipalPayment", "graceOnInterestPayment",
|
||||
"graceOnInterestCharged", "interestChargedFromDate", "timeline", "totalFeeChargesAtDisbursement", "summary",
|
||||
"repaymentSchedule", "transactions", "charges", "collateral", "guarantors", "meeting", "productOptions",
|
||||
"amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions",
|
||||
"termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions",
|
||||
"loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions"));
|
||||
|
||||
private final String resourceNameForPermissions = "LOAN";
|
||||
|
||||
@@ -126,8 +127,7 @@ public class LoansApiResource {
|
||||
final CollateralReadPlatformService loanCollateralReadPlatformService,
|
||||
final LoanScheduleCalculationPlatformService calculationPlatformService,
|
||||
final StaffReadPlatformService staffReadPlatformService, final GuarantorReadPlatformService guarantorReadPlatformService,
|
||||
final CodeValueReadPlatformService codeValueReadPlatformService,
|
||||
final GroupReadPlatformService groupReadPlatformService,
|
||||
final CodeValueReadPlatformService codeValueReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
|
||||
final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer,
|
||||
final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer,
|
||||
final DefaultToApiJsonSerializer<BulkTransferLoanOfficerData> loanOfficeTransferToApiJsonSerializer,
|
||||
@@ -161,7 +161,8 @@ public class LoansApiResource {
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String template(@QueryParam("clientId") final Long clientId, @QueryParam("groupId") final Long groupId,
|
||||
@QueryParam("productId") final Long productId, @QueryParam("templateType") final String templateType, @Context final UriInfo uriInfo) {
|
||||
@QueryParam("productId") final Long productId, @QueryParam("templateType") final String templateType,
|
||||
@Context final UriInfo uriInfo) {
|
||||
|
||||
context.authenticatedUser().validateHasReadPermission(resourceNameForPermissions);
|
||||
|
||||
@@ -174,24 +175,24 @@ public class LoansApiResource {
|
||||
Collection<CalendarData> calendarOptions = null;
|
||||
LoanAccountData newLoanAccount = null;
|
||||
Long officeId = null;
|
||||
|
||||
|
||||
if (productId != null) {
|
||||
newLoanAccount = this.loanReadPlatformService.retrieveLoanProductDetailsTemplate(productId);
|
||||
}
|
||||
|
||||
if(templateType == null){
|
||||
|
||||
if (templateType == null) {
|
||||
final String errorMsg = "Loan template type must be provided";
|
||||
throw new LoanTemplateTypeRequiredException(errorMsg);
|
||||
}else if (templateType.equals("collateral")) {
|
||||
} else if (templateType.equals("collateral")) {
|
||||
newLoanAccount = LoanAccountData.collateralTemplate(loanCollateralOptions);
|
||||
} else {
|
||||
//for JLG loan both client and group details are required
|
||||
// for JLG loan both client and group details are required
|
||||
if (templateType.equals("individual") || templateType.equals("jlg")) {
|
||||
|
||||
LoanAccountData loanAccountClientDetails = this.loanReadPlatformService.retrieveClientDetailsTemplate(clientId);
|
||||
|
||||
officeId = loanAccountClientDetails.officeId();
|
||||
|
||||
|
||||
newLoanAccount = (newLoanAccount == null) ? loanAccountClientDetails : LoanAccountData.populateClientDefaults(
|
||||
newLoanAccount, loanAccountClientDetails);
|
||||
|
||||
@@ -211,7 +212,7 @@ public class LoansApiResource {
|
||||
loanAccountGroupData);
|
||||
|
||||
} else if (templateType.equals("jlgbulk")) {
|
||||
//get group details along with members in that group
|
||||
// get group details along with members in that group
|
||||
final LoanAccountData loanAccountGroupData = this.loanReadPlatformService.retrieveGroupAndMembersDetailsTemplate(groupId);
|
||||
officeId = loanAccountGroupData.groupOfficeId();
|
||||
calendarOptions = this.loanReadPlatformService.retrieveCalendars(groupId);
|
||||
@@ -223,7 +224,8 @@ public class LoansApiResource {
|
||||
}
|
||||
|
||||
if (officeId != null) allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(officeId);
|
||||
// add product options, allowed loan officers and calendar options (calendar options will be null in individual loan)
|
||||
// add product options, allowed loan officers and calendar options
|
||||
// (calendar options will be null in individual loan)
|
||||
newLoanAccount = LoanAccountData.associationsAndTemplate(newLoanAccount, productOptions, allowedLoanOfficers, calendarOptions);
|
||||
}
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
@@ -246,7 +248,7 @@ public class LoansApiResource {
|
||||
Collection<GuarantorData> guarantors = null;
|
||||
Collection<CollateralData> collateral = null;
|
||||
CalendarData meeting = null;
|
||||
|
||||
|
||||
final Set<String> mandatoryResponseParameters = new HashSet<String>();
|
||||
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
|
||||
if (!associationParameters.isEmpty()) {
|
||||
@@ -293,7 +295,7 @@ public class LoansApiResource {
|
||||
collateral = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (associationParameters.contains("meeting")) {
|
||||
mandatoryResponseParameters.add("meeting");
|
||||
meeting = this.calendarReadPlatformService.retrieveLoanCalendar(loanId);
|
||||
@@ -332,20 +334,20 @@ public class LoansApiResource {
|
||||
final boolean feeChargesOnly = false;
|
||||
chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly);
|
||||
chargeTemplate = this.loanChargeReadPlatformService.retrieveLoanChargeTemplate();
|
||||
|
||||
if(loanBasicDetails.officeId() != null){
|
||||
|
||||
if (loanBasicDetails.officeId() != null) {
|
||||
allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(loanBasicDetails.officeId());
|
||||
}else if(loanBasicDetails.groupOfficeId() != null){
|
||||
} else if (loanBasicDetails.groupOfficeId() != null) {
|
||||
allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(loanBasicDetails.groupOfficeId());
|
||||
}
|
||||
|
||||
|
||||
loanPurposeOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanPurpose");
|
||||
loanCollateralOptions = this.codeValueReadPlatformService.retrieveCodeValuesByCode("LoanCollateral");
|
||||
|
||||
if(loanBasicDetails.groupId() != null){
|
||||
|
||||
if (loanBasicDetails.groupId() != null) {
|
||||
calendarOptions = this.loanReadPlatformService.retrieveCalendars(loanBasicDetails.groupId());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
|
||||
@@ -376,7 +378,7 @@ public class LoansApiResource {
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.toApiJsonSerializer.serialize(settings, loanBasicDetails, LOAN_DATA_PARAMETERS);
|
||||
}
|
||||
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@@ -388,10 +390,10 @@ public class LoansApiResource {
|
||||
final JsonElement parsedQuery = this.fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||
final JsonQuery query = JsonQuery.from(apiRequestBodyAsJson, parsedQuery, this.fromJsonHelper);
|
||||
|
||||
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query);
|
||||
final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.loanScheduleToApiJsonSerializer.serialize(settings, loanSchedule, new HashSet<String>());
|
||||
return this.loanScheduleToApiJsonSerializer.serialize(settings, loanSchedule.toData(), new HashSet<String>());
|
||||
}
|
||||
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().createLoanApplication().withJson(apiRequestBodyAsJson).build();
|
||||
|
||||
+121
-95
@@ -82,8 +82,11 @@ public class LoanAccountData {
|
||||
private final EnumOptionData interestCalculationPeriodType;
|
||||
private final BigDecimal inArrearsTolerance;
|
||||
private final Long transactionProcessingStrategyId;
|
||||
private final LocalDate expectedFirstRepaymentOnDate;
|
||||
private final Integer graceOnPrincipalPayment;
|
||||
private final Integer graceOnInterestPayment;
|
||||
private final Integer graceOnInterestCharged;
|
||||
private final LocalDate interestChargedFromDate;
|
||||
private final LocalDate expectedFirstRepaymentOnDate;
|
||||
|
||||
// timeline
|
||||
private final LoanApplicationTimelineData timeline;
|
||||
@@ -157,6 +160,9 @@ public class LoanAccountData {
|
||||
final EnumOptionData interestType = null;
|
||||
final EnumOptionData interestCalculationPeriodType = null;
|
||||
final LocalDate expectedFirstRepaymentOnDate = null;
|
||||
final Integer graceOnPrincipalPayment = null;
|
||||
final Integer graceOnInterestPayment = null;
|
||||
final Integer graceOnInterestCharged = null;
|
||||
final LocalDate interestChargedFromDate = null;
|
||||
final LoanApplicationTimelineData timeline = null;
|
||||
final LoanSummaryData summary = null;
|
||||
@@ -188,11 +194,11 @@ public class LoanAccountData {
|
||||
currencyData, principal, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery,
|
||||
repaymentFrequencyType, transactionProcessingStrategyId, amortizationType, interestRatePerPeriod,
|
||||
interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate,
|
||||
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
|
||||
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
|
||||
transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
|
||||
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
|
||||
loanCollateralOptions, calendarOptions);
|
||||
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary,
|
||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions,
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,6 +239,9 @@ public class LoanAccountData {
|
||||
final EnumOptionData interestType = null;
|
||||
final EnumOptionData interestCalculationPeriodType = null;
|
||||
final LocalDate expectedFirstRepaymentOnDate = null;
|
||||
final Integer graceOnPrincipalPayment = null;
|
||||
final Integer graceOnInterestPayment = null;
|
||||
final Integer graceOnInterestCharged = null;
|
||||
final LocalDate interestChargedFromDate = null;
|
||||
final LoanApplicationTimelineData timeline = LoanApplicationTimelineData.templateDefault(expectedDisbursementDate);
|
||||
final LoanSummaryData summary = null;
|
||||
@@ -265,8 +274,9 @@ public class LoanAccountData {
|
||||
currencyData, principal, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery,
|
||||
repaymentFrequencyType, transactionProcessingStrategyId, amortizationType, interestRatePerPeriod,
|
||||
interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate,
|
||||
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
|
||||
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary,
|
||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
}
|
||||
@@ -274,19 +284,20 @@ public class LoanAccountData {
|
||||
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
|
||||
|
||||
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, clientAcc.clientId, clientAcc.clientName,
|
||||
clientAcc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId,
|
||||
acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
|
||||
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
|
||||
acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, acc.amortizationType, acc.interestRatePerPeriod,
|
||||
acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType,
|
||||
acc.expectedFirstRepaymentOnDate, acc.interestChargedFromDate, clientAcc.timeline, acc.summary,
|
||||
acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting,
|
||||
acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||
clientAcc.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.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments,
|
||||
acc.repaymentEvery, acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, acc.amortizationType,
|
||||
acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType,
|
||||
acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment,
|
||||
acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, clientAcc.timeline, acc.summary,
|
||||
acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
|
||||
acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to produce a {@link LoanAccountData} with only group information
|
||||
* defaulted.
|
||||
@@ -326,6 +337,9 @@ public class LoanAccountData {
|
||||
final EnumOptionData interestType = null;
|
||||
final EnumOptionData interestCalculationPeriodType = null;
|
||||
final LocalDate expectedFirstRepaymentOnDate = null;
|
||||
final Integer graceOnPrincipalPayment = null;
|
||||
final Integer graceOnInterestPayment = null;
|
||||
final Integer graceOnInterestCharged = null;
|
||||
final LocalDate interestChargedFromDate = null;
|
||||
final LoanApplicationTimelineData timeline = LoanApplicationTimelineData.templateDefault(expectedDisbursementDate);
|
||||
final LoanSummaryData summary = null;
|
||||
@@ -358,28 +372,30 @@ public class LoanAccountData {
|
||||
currencyData, principal, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery,
|
||||
repaymentFrequencyType, transactionProcessingStrategyId, amortizationType, interestRatePerPeriod,
|
||||
interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate,
|
||||
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
|
||||
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary,
|
||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
|
||||
|
||||
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName,
|
||||
acc.clientOfficeId, groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId,
|
||||
acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
|
||||
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
|
||||
groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName,
|
||||
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal,
|
||||
acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
|
||||
acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, acc.amortizationType, acc.interestRatePerPeriod,
|
||||
acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType,
|
||||
acc.expectedFirstRepaymentOnDate, acc.interestChargedFromDate, groupAcc.timeline, acc.summary,
|
||||
acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting,
|
||||
acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions);
|
||||
acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged,
|
||||
acc.interestChargedFromDate, groupAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule,
|
||||
acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions,
|
||||
acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.transactionProcessingStrategyOptions,
|
||||
acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions,
|
||||
acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions,
|
||||
acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions);
|
||||
}
|
||||
|
||||
|
||||
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
|
||||
final Collection<EnumOptionData> termFrequencyTypeOptions, final Collection<EnumOptionData> repaymentFrequencyTypeOptions,
|
||||
final Collection<TransactionProcessingStrategyData> repaymentStrategyOptions,
|
||||
@@ -387,7 +403,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 Long id = null;
|
||||
final String accountNo = null;
|
||||
final LoanStatusEnumData status = null;
|
||||
@@ -423,7 +439,7 @@ public class LoanAccountData {
|
||||
final LoanSummaryData summary = null;
|
||||
final BigDecimal feeChargesDueAtDisbursementCharged = null;
|
||||
final ChargeData chargeTemplate = null;
|
||||
|
||||
|
||||
final LoanScheduleData repaymentSchedule = null;
|
||||
final Collection<LoanTransactionData> transactions = null;
|
||||
final Collection<CollateralData> collateral = null;
|
||||
@@ -432,7 +448,7 @@ public class LoanAccountData {
|
||||
final Collection<LoanProductData> productOptions = null;
|
||||
final Collection<CalendarData> calendarOptions = null;
|
||||
final Collection<StaffData> loanOfficerOptions = null;
|
||||
|
||||
|
||||
final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
|
||||
final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
|
||||
|
||||
@@ -441,20 +457,21 @@ public class LoanAccountData {
|
||||
charges.add(charge.toLoanChargeData());
|
||||
}
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId,
|
||||
group, loanType, product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(),
|
||||
loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(),
|
||||
product.getPrincipal(), product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType,
|
||||
product.getNumberOfRepayments(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(),
|
||||
product.getTransactionProcessingStrategyId(), product.getAmortizationType(), product.getInterestRatePerPeriod(),
|
||||
product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(),
|
||||
product.getInterestCalculationPeriodType(), expectedFirstRepaymentOnDate, interestChargedFromDate, timeline,
|
||||
summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType,
|
||||
product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId,
|
||||
loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(), product.getPrincipal(),
|
||||
product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(),
|
||||
product.getRepaymentEvery(), product.getRepaymentFrequencyType(), product.getTransactionProcessingStrategyId(),
|
||||
product.getAmortizationType(), product.getInterestRatePerPeriod(), product.getInterestRateFrequencyType(),
|
||||
product.getAnnualInterestRate(), product.getInterestType(), product.getInterestCalculationPeriodType(),
|
||||
expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getGraceOnInterestPayment(),
|
||||
product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged,
|
||||
repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
|
||||
repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions,
|
||||
interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions,
|
||||
loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
}
|
||||
|
||||
|
||||
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
|
||||
|
||||
final LoanScheduleData repaymentSchedule = null;
|
||||
@@ -477,7 +494,7 @@ public class LoanAccountData {
|
||||
final Collection<CodeValueData> loanPurposeOptions = null;
|
||||
final Collection<CodeValueData> loanCollateralOptions = null;
|
||||
final Collection<CalendarData> calendarOptions = null;
|
||||
|
||||
|
||||
final Integer termFrequency = product.getNumberOfRepayments() * product.getRepaymentEvery();
|
||||
final EnumOptionData termPeriodFrequencyType = product.getRepaymentFrequencyType();
|
||||
|
||||
@@ -487,15 +504,16 @@ public class LoanAccountData {
|
||||
}
|
||||
|
||||
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
|
||||
acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(),
|
||||
acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, product.getCurrency(),
|
||||
product.getPrincipal(), product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType,
|
||||
acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), product.getFundId(),
|
||||
product.getFundName(), acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName,
|
||||
product.getCurrency(), product.getPrincipal(), product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType,
|
||||
product.getNumberOfRepayments(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(),
|
||||
product.getTransactionProcessingStrategyId(), product.getAmortizationType(), product.getInterestRatePerPeriod(),
|
||||
product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(),
|
||||
product.getInterestCalculationPeriodType(), acc.expectedFirstRepaymentOnDate, acc.interestChargedFromDate, acc.timeline,
|
||||
acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
product.getInterestCalculationPeriodType(), acc.expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(),
|
||||
product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), acc.interestChargedFromDate, acc.timeline,
|
||||
acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors,
|
||||
calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
}
|
||||
@@ -506,16 +524,16 @@ public class LoanAccountData {
|
||||
*/
|
||||
public static LoanAccountData basicLoanDetails(final Long id, final String accountNo, final LoanStatusEnumData status,
|
||||
final String externalId, final Long clientId, final String clientName, final Long clientOfficeId, final GroupGeneralData group,
|
||||
final EnumOptionData loanType, final Long loanProductId, final String loanProductName, final String loanProductDescription, final Long fundId,
|
||||
final String fundName, final Long loanPurposeId, final String loanPurposeName, final Long loanOfficerId,
|
||||
final EnumOptionData loanType, final Long loanProductId, final String loanProductName, final String loanProductDescription,
|
||||
final Long fundId, final String fundName, final Long loanPurposeId, final String loanPurposeName, final Long loanOfficerId,
|
||||
final String loanOfficerName, final CurrencyData currencyData, final BigDecimal principal, final BigDecimal inArrearsTolerance,
|
||||
final Integer termFrequency, final EnumOptionData termPeriodFrequencyType, final Integer numberOfRepayments,
|
||||
final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, final Long transactionStrategyId,
|
||||
final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType,
|
||||
final BigDecimal annualInterestRate, final EnumOptionData interestType, final EnumOptionData interestCalculationPeriodType,
|
||||
final LocalDate expectedFirstRepaymentOnDate, final LocalDate interestChargedFromDate,
|
||||
final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary,
|
||||
final BigDecimal feeChargesDueAtDisbursementCharged) {
|
||||
final LocalDate expectedFirstRepaymentOnDate, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment,
|
||||
final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline,
|
||||
final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged) {
|
||||
|
||||
final LoanScheduleData repaymentSchedule = null;
|
||||
final Collection<LoanTransactionData> transactions = null;
|
||||
@@ -538,14 +556,15 @@ public class LoanAccountData {
|
||||
final Collection<CodeValueData> loanPurposeOptions = null;
|
||||
final Collection<CodeValueData> loanCollateralOptions = null;
|
||||
final Collection<CalendarData> calendarOptions = null;
|
||||
|
||||
|
||||
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId,
|
||||
loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName,
|
||||
currencyData, principal, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery,
|
||||
repaymentFrequencyType, transactionStrategyId, amortizationType, interestRatePerPeriod, interestRateFrequencyType,
|
||||
annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, interestChargedFromDate,
|
||||
timeline, loanSummary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral,
|
||||
guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment,
|
||||
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary,
|
||||
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
|
||||
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions,
|
||||
interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions,
|
||||
fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
}
|
||||
@@ -566,41 +585,44 @@ public class LoanAccountData {
|
||||
final Collection<StaffData> loanOfficerOptions, final Collection<CodeValueData> loanPurposeOptions,
|
||||
final Collection<CodeValueData> loanCollateralOptions, final Collection<CalendarData> calendarOptions) {
|
||||
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.inArrearsTolerance,
|
||||
acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
|
||||
acc.transactionProcessingStrategyId, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
|
||||
acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
|
||||
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.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
|
||||
acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, 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, repaymentSchedule,
|
||||
transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
|
||||
transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
|
||||
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions,
|
||||
loanCollateralOptions, calendarOptions);
|
||||
transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions,
|
||||
repaymentFrequencyTypeOptions, transactionProcessingStrategyOptions, interestRateFrequencyTypeOptions,
|
||||
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions,
|
||||
chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions);
|
||||
}
|
||||
|
||||
public static LoanAccountData associationsAndTemplate(LoanAccountData acc, Collection<LoanProductData> productOptions,
|
||||
Collection<StaffData> allowedLoanOfficers, final Collection<CalendarData> calendarOptions) {
|
||||
return associationsAndTemplate(acc, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting,
|
||||
productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.transactionProcessingStrategyOptions,
|
||||
acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions,
|
||||
acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, allowedLoanOfficers,
|
||||
acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions);
|
||||
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
|
||||
final Collection<StaffData> allowedLoanOfficers, final Collection<CalendarData> calendarOptions) {
|
||||
return associationsAndTemplate(acc, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors,
|
||||
acc.meeting, productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions,
|
||||
acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
|
||||
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
|
||||
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions);
|
||||
}
|
||||
|
||||
|
||||
public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) {
|
||||
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId,
|
||||
group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, acc.loanPurposeId,
|
||||
acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal, acc.inArrearsTolerance,
|
||||
acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
|
||||
acc.transactionProcessingStrategyId, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType,
|
||||
acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate,
|
||||
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.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery,
|
||||
acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, 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.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);
|
||||
}
|
||||
|
||||
|
||||
private LoanAccountData(
|
||||
final Long id, //
|
||||
final String accountNo, //
|
||||
@@ -631,6 +653,7 @@ public class LoanAccountData {
|
||||
final Long transactionProcessingStrategyId, final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod,
|
||||
final EnumOptionData interestRateFrequencyType, final BigDecimal annualInterestRate, final EnumOptionData interestType,
|
||||
final EnumOptionData interestCalculationPeriodType, final LocalDate expectedFirstRepaymentOnDate,
|
||||
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
|
||||
final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData summary,
|
||||
final BigDecimal feeChargesDueAtDisbursementCharged, final LoanScheduleData repaymentSchedule,
|
||||
final Collection<LoanTransactionData> transactions, final Collection<LoanChargeData> charges,
|
||||
@@ -677,6 +700,9 @@ public class LoanAccountData {
|
||||
this.interestType = interestType;
|
||||
this.interestCalculationPeriodType = interestCalculationPeriodType;
|
||||
this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate;
|
||||
this.graceOnPrincipalPayment = graceOnPrincipalPayment;
|
||||
this.graceOnInterestPayment = graceOnInterestPayment;
|
||||
this.graceOnInterestCharged = graceOnInterestCharged;
|
||||
this.interestChargedFromDate = interestChargedFromDate;
|
||||
this.timeline = timeline;
|
||||
this.feeChargesAtDisbursementCharged = feeChargesDueAtDisbursementCharged;
|
||||
@@ -736,7 +762,7 @@ public class LoanAccountData {
|
||||
} else {
|
||||
this.loanCollateralOptions = loanCollateralOptions;
|
||||
}
|
||||
|
||||
|
||||
if (CollectionUtils.isEmpty(calendarOptions)) {
|
||||
this.calendarOptions = null;
|
||||
} else {
|
||||
@@ -760,12 +786,12 @@ public class LoanAccountData {
|
||||
public Collection<LoanChargeData> charges() {
|
||||
return this.charges;
|
||||
}
|
||||
|
||||
public Long groupOfficeId(){
|
||||
return group == null? null : group.officeId();
|
||||
|
||||
public Long groupOfficeId() {
|
||||
return group == null ? null : group.officeId();
|
||||
}
|
||||
|
||||
public Long groupId(){
|
||||
return group == null? null : group.getId();
|
||||
|
||||
public Long groupId() {
|
||||
return group == null ? null : group.getId();
|
||||
}
|
||||
}
|
||||
+43
-91
@@ -6,6 +6,8 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -61,14 +63,12 @@ import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionT
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DefaultLoanScheduleGeneratorFactory;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
@@ -266,21 +266,20 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
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, final LoanSchedule loanSchedule,
|
||||
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral) {
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
|
||||
final Set<LoanCollateral> collateral) {
|
||||
final LoanStatus status = null;
|
||||
final Group group = null;
|
||||
LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail();
|
||||
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||
loanRepaymentScheduleDetail, status, loanCharges, collateral);
|
||||
}
|
||||
|
||||
public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType,
|
||||
final LoanProduct loanProduct, final Fund fund, final Staff officer,
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule,
|
||||
final Set<LoanCharge> loanCharges) {
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges) {
|
||||
final LoanStatus status = null;
|
||||
final LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail();
|
||||
final CodeValue loanPurpose = null;
|
||||
final Set<LoanCollateral> collateral = null;
|
||||
final Client client = null;
|
||||
@@ -290,10 +289,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group,
|
||||
final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer,
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanSchedule loanSchedule,
|
||||
final Set<LoanCharge> loanCharges) {
|
||||
final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges) {
|
||||
final LoanStatus status = null;
|
||||
final LoanProductRelatedDetail loanRepaymentScheduleDetail = loanSchedule.loanProductRelatedDetail();
|
||||
final CodeValue loanPurpose = null;
|
||||
final Set<LoanCollateral> collateral = null;
|
||||
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
|
||||
@@ -308,6 +306,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final Staff loanOfficer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
|
||||
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
|
||||
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral) {
|
||||
|
||||
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
|
||||
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
|
||||
|
||||
if (StringUtils.isBlank(accountNo)) {
|
||||
this.accountNumber = new RandomPasswordGenerator(19).generate();
|
||||
this.accountNumberRequiresAutoGeneration = true;
|
||||
@@ -323,7 +325,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
this.transactionProcessingStrategy = transactionProcessingStrategy;
|
||||
this.loanProduct = loanProduct;
|
||||
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
|
||||
if (loanStatus != null) {
|
||||
this.loanStatus = loanStatus.getValue();
|
||||
} else {
|
||||
@@ -735,10 +736,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.collateral.addAll(associateWithThisLoan(loanCollateral));
|
||||
}
|
||||
|
||||
public void updateLoanSchedule(final LoanScheduleData modifiedLoanSchedule) {
|
||||
public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule) {
|
||||
this.repaymentScheduleInstallments.clear();
|
||||
|
||||
for (LoanSchedulePeriodData scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
|
||||
for (LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
|
||||
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod()) {
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this,
|
||||
@@ -750,24 +751,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
// if the loan application/contract is modified when repayments are
|
||||
// already against it - then need to re-process it
|
||||
|
||||
/****
|
||||
* TODO Vishwas Currently we do not allow modifying a Loan contract once
|
||||
* the loan is approved (hence there is no need to adjust any loan
|
||||
* transactions).
|
||||
*
|
||||
* Consider removing this block of code or logically completing it for
|
||||
* the future by getting the list of affected Transactions
|
||||
***/
|
||||
final List<LoanTransaction> repaymentsOrWaivers = retreiveListOfTransactionsPostDisbursement();
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategy);
|
||||
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), repaymentsOrWaivers, getCurrency(),
|
||||
this.repaymentScheduleInstallments, setOfLoanCharges());
|
||||
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
updateLoanSummaryDerivedFields();
|
||||
}
|
||||
@@ -1042,18 +1025,11 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
public void loanApplicationSubmittal(final AppUser currentUser, final LoanSchedule loanSchedule,
|
||||
final LoanLifecycleStateMachine lifecycleStateMachine, final LocalDate submittedOn, final String externalId) {
|
||||
public void loanApplicationSubmittal(final AppUser currentUser, final LoanScheduleModel loanSchedule,
|
||||
final LoanApplicationTerms loanApplicationTerms, final LoanLifecycleStateMachine lifecycleStateMachine,
|
||||
final LocalDate submittedOn, final String externalId) {
|
||||
|
||||
final LoanScheduleData loanScheduleData = loanSchedule.generate();
|
||||
|
||||
// Have to set expectedDisbursementDate to avoid nullPointer so should
|
||||
// be passed down to updateLoanSchedule method
|
||||
if (loanSchedule.getDisbursementDate() != null) {
|
||||
this.expectedDisbursementDate = loanSchedule.getDisbursementDate().toDate();
|
||||
}
|
||||
|
||||
updateLoanSchedule(loanScheduleData);
|
||||
updateLoanSchedule(loanSchedule);
|
||||
|
||||
LoanStatus from = null;
|
||||
if (this.loanStatus != null) {
|
||||
@@ -1064,22 +1040,16 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
this.loanStatus = statusEnum.getValue();
|
||||
|
||||
this.externalId = externalId;
|
||||
|
||||
this.termFrequency = loanSchedule.getLoanTermFrequency();
|
||||
this.termPeriodFrequencyType = loanSchedule.getLoanTermPeriodFrequencyType().getValue();
|
||||
|
||||
this.termFrequency = loanApplicationTerms.getLoanTermFrequency();
|
||||
this.termPeriodFrequencyType = loanApplicationTerms.getLoanTermPeriodFrequencyType().getValue();
|
||||
this.submittedOnDate = submittedOn.toDate();
|
||||
this.submittedBy = currentUser;
|
||||
this.expectedDisbursementDate = loanApplicationTerms.getExpectedDisbursementDate().toDate();
|
||||
this.expectedFirstRepaymentOnDate = loanApplicationTerms.getRepaymentStartFromDate();
|
||||
this.interestChargedFromDate = loanApplicationTerms.getInterestChargedFromDate();
|
||||
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
|
||||
if (loanSchedule.getRepaymentStartFromDate() != null) {
|
||||
this.expectedFirstRepaymentOnDate = loanSchedule.getRepaymentStartFromDate().toDate();
|
||||
}
|
||||
|
||||
if (loanSchedule.getInterestChargedFromDate() != null) {
|
||||
this.interestChargedFromDate = loanSchedule.getInterestChargedFromDate().toDate();
|
||||
}
|
||||
|
||||
if (submittedOn.isAfter(DateUtils.getLocalDateOfTenant())) {
|
||||
final String errorMessage = "The date on which a loan is submitted cannot be in the future.";
|
||||
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, submittedOn);
|
||||
@@ -1297,9 +1267,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
return ids;
|
||||
}
|
||||
|
||||
public Map<String, Object> disburse(final AppUser currentUser, final JsonCommand command, final ApplicationCurrency currency,
|
||||
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||
final Map<String, Object> actualChanges, final PaymentDetail paymentDetail) {
|
||||
public Map<String, Object> disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
|
||||
final JsonCommand command, final ApplicationCurrency currency, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges, final PaymentDetail paymentDetail) {
|
||||
|
||||
updateLoanToPreDisbursalState();
|
||||
|
||||
@@ -1325,7 +1295,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
handleDisbursementTransaction(paymentDetail, actualDisbursementDate);
|
||||
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
||||
regenerateRepaymentSchedule(currency);
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency);
|
||||
} else {
|
||||
updateLoanSummaryDerivedFields();
|
||||
}
|
||||
@@ -1349,35 +1319,25 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
* Ability to regenerate the repayment schedule based on the loans current
|
||||
* details/state.
|
||||
*/
|
||||
private void regenerateRepaymentSchedule(final ApplicationCurrency applicationCurrency) {
|
||||
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||
final ApplicationCurrency applicationCurrency) {
|
||||
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
final LoanScheduleGenerator loanScheduleGenerator = new DefaultLoanScheduleGeneratorFactory().create(interestMethod);
|
||||
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
||||
|
||||
final BigDecimal principal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount();
|
||||
final BigDecimal inArrearsTolerance = this.loanRepaymentScheduleDetail.getInArrearsTolerance().getAmount();
|
||||
final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod();
|
||||
final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
|
||||
final BigDecimal defaultAnnualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
|
||||
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
|
||||
final MathContext mc = new MathContext(8, roundingMode);
|
||||
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = this.loanRepaymentScheduleDetail
|
||||
.getInterestCalculationPeriodMethod();
|
||||
final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery();
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
|
||||
final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments();
|
||||
final AmortizationMethod amortizationMethod = this.loanRepaymentScheduleDetail.getAmortizationMethod();
|
||||
final Integer loanTermFrequency = this.termFrequency;
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
|
||||
|
||||
final LoanSchedule loanSchedule = new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod,
|
||||
interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency,
|
||||
loanTermPeriodFrequencyType, setOfLoanCharges(), this.getDisbursementDate(), this.getExpectedFirstRepaymentOnDate(),
|
||||
this.getInterestChargedFromDate(), inArrearsTolerance);
|
||||
LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
|
||||
loanTermPeriodFrequencyType, getExpectedDisbursedOnLocalDate(), getExpectedFirstRepaymentOnDate(), getInArrearsTolerance(),
|
||||
this.loanRepaymentScheduleDetail);
|
||||
|
||||
final LoanScheduleData generatedData = loanSchedule.generate();
|
||||
LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges);
|
||||
|
||||
updateLoanSchedule(generatedData);
|
||||
updateLoanSchedule(loanSchedule);
|
||||
}
|
||||
|
||||
private LoanTransaction handleDisbursementTransaction(final PaymentDetail paymentDetail, final LocalDate disbursedOn) {
|
||||
@@ -1433,14 +1393,6 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.a.future.date", errorMessage, disbursedOn);
|
||||
}
|
||||
|
||||
LocalDate firstRepaymentDueDate = this.repaymentScheduleInstallments.get(0).getDueDate();
|
||||
if (disbursedOn.isAfter(firstRepaymentDueDate)) {
|
||||
final String errorMessage = "The date on which a loan with identifier : " + this.accountNumber
|
||||
+ " is disbursed cannot be after the first expected repayment date: " + firstRepaymentDueDate.toString();
|
||||
throw new InvalidLoanStateTransitionException("disbursal", "cannot.be.after.first.repayment.due.date", errorMessage,
|
||||
this.accountNumber, disbursedOn, firstRepaymentDueDate);
|
||||
}
|
||||
|
||||
return disbursementTransaction;
|
||||
}
|
||||
|
||||
|
||||
-8
@@ -286,14 +286,6 @@ public class LoanSchedulePeriodData {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean isRepaymentPeriod() {
|
||||
boolean isRepaymentPeriod = false;
|
||||
if (principalDue != null && this.interestDue != null) {
|
||||
isRepaymentPeriod = BigDecimal.ZERO.compareTo(this.principalDue) == -1 || BigDecimal.ZERO.compareTo(this.interestDue) == -1;
|
||||
}
|
||||
return isRepaymentPeriod;
|
||||
}
|
||||
|
||||
public Integer periodNumber() {
|
||||
return this.period;
|
||||
}
|
||||
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
@Override
|
||||
public LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency,
|
||||
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges) {
|
||||
|
||||
// 1. generate list of proposed schedule due dates
|
||||
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms);
|
||||
|
||||
// 2. determine the total charges due at time of disbursement
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||
|
||||
// 3. setup variables for tracking important facts required for loan
|
||||
// schedule generation.
|
||||
final Money principalDisbursed = loanApplicationTerms.getPrincipal();
|
||||
final MonetaryCurrency currency = principalDisbursed.getCurrency();
|
||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||
|
||||
// variables for cumulative totals
|
||||
int loanTermInDays = Integer.valueOf(0);
|
||||
BigDecimal totalPrincipalExpected = BigDecimal.ZERO;
|
||||
BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
|
||||
BigDecimal totalInterestCharged = BigDecimal.ZERO;
|
||||
BigDecimal totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
|
||||
BigDecimal totalPenaltyChargesCharged = BigDecimal.ZERO;
|
||||
BigDecimal totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
|
||||
BigDecimal totalOutstanding = BigDecimal.ZERO;
|
||||
|
||||
final Collection<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments,
|
||||
loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
||||
|
||||
// 4. Determine the total interest owed over the full loan.
|
||||
final Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
|
||||
this.paymentPeriodsInOneYearCalculator, mc);
|
||||
|
||||
// 5. loop over the known set of 'scheduled due dates'
|
||||
LocalDate repaymentPeriodStartDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate(
|
||||
loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), scheduledDates);
|
||||
|
||||
LocalDate startDate = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
int periodNumber = 1;
|
||||
Money totalCumulativePrincipal = principalDisbursed.zero();
|
||||
Money totalCumulativeInterest = principalDisbursed.zero();
|
||||
Money totalOutstandingInterestPaymentDueToGrace = principalDisbursed.zero();
|
||||
Money outstandingBalance = principalDisbursed;
|
||||
for (LocalDate scheduledDueDate : scheduledDates) {
|
||||
|
||||
final int daysInPeriod = Days.daysBetween(startDate, scheduledDueDate).getDays();
|
||||
|
||||
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
|
||||
.calculatePortionOfRepaymentPeriodInterestChargingGrace(repaymentPeriodStartDate, scheduledDueDate,
|
||||
loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(),
|
||||
loanApplicationTerms.getRepaymentEvery());
|
||||
|
||||
// 5 determine principal,interest of repayment period
|
||||
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
|
||||
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
|
||||
totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, daysInPeriod,
|
||||
outstandingBalance, loanApplicationTerms, periodNumber, mc);
|
||||
|
||||
// update cumulative fields for principal & interest
|
||||
totalCumulativePrincipal = totalCumulativePrincipal.plus(principalInterestForThisPeriod.principal());
|
||||
totalCumulativeInterest = totalCumulativeInterest.plus(principalInterestForThisPeriod.interest());
|
||||
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
|
||||
|
||||
// 6. update outstandingLoanBlance using correct 'principalDue'
|
||||
outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal());
|
||||
|
||||
// 7. determine fees and penalties
|
||||
final Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(startDate, scheduledDueDate, loanCharges, currency);
|
||||
final Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(startDate, scheduledDueDate, loanCharges, currency);
|
||||
|
||||
// 8. sum up real totalInstallmentDue from components
|
||||
final Money totalInstallmentDue = principalInterestForThisPeriod.principal() //
|
||||
.plus(principalInterestForThisPeriod.interest()) //
|
||||
.plus(feeChargesForInstallment) //
|
||||
.plus(penaltyChargesForInstallment);
|
||||
|
||||
// 9. create repayment period from parts
|
||||
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, startDate,
|
||||
scheduledDueDate, principalInterestForThisPeriod.principal(), outstandingBalance,
|
||||
principalInterestForThisPeriod.interest(), feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue);
|
||||
periods.add(installment);
|
||||
|
||||
// handle cumulative fields
|
||||
loanTermInDays += daysInPeriod;
|
||||
totalPrincipalExpected = totalPrincipalExpected.add(principalInterestForThisPeriod.principal().getAmount());
|
||||
totalInterestCharged = totalInterestCharged.add(principalInterestForThisPeriod.interest().getAmount());
|
||||
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
|
||||
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());
|
||||
totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount());
|
||||
startDate = scheduledDueDate;
|
||||
repaymentPeriodStartDate = startDate;
|
||||
|
||||
periodNumber++;
|
||||
}
|
||||
|
||||
return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
|
||||
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
|
||||
totalOutstanding);
|
||||
}
|
||||
|
||||
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(PaymentPeriodsInOneYearCalculator calculator,
|
||||
double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest,
|
||||
Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, int daysInPeriod, Money outstandingBalance,
|
||||
LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc);
|
||||
|
||||
protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) {
|
||||
return periodNumber == numberOfRepayments;
|
||||
}
|
||||
|
||||
private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
|
||||
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueAtDisbursement()) {
|
||||
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
return chargesDueAtTimeOfDisbursement;
|
||||
}
|
||||
|
||||
private Collection<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final int numberOfRepayments,
|
||||
final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
|
||||
final Collection<LoanScheduleModelPeriod> periods = new ArrayList<LoanScheduleModelPeriod>(numberOfRepayments + 1);
|
||||
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
||||
loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
|
||||
return periods;
|
||||
}
|
||||
|
||||
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
|
||||
final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isFeeCharge()) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
|
||||
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
return cumulative;
|
||||
}
|
||||
}
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
|
||||
public interface AmortizationLoanScheduleGenerator {
|
||||
|
||||
LoanScheduleData generate(ApplicationCurrency applicationCurrency, LoanProductRelatedDetail loanScheduleInfo,
|
||||
LocalDate disbursementDate, LocalDate interestCalculatedFrom, BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, List<LocalDate> scheduledDates, Set<LoanCharge> loanCharges);
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
|
||||
public class AmortizationLoanScheduleGeneratorFactory {
|
||||
|
||||
public AmortizationLoanScheduleGenerator createGenerator(final AmortizationMethod amortizationMethod) {
|
||||
|
||||
AmortizationLoanScheduleGenerator generator = new EqualInstallmentsAmortizationLoanScheduleGenerator();
|
||||
|
||||
switch (amortizationMethod) {
|
||||
case INVALID:
|
||||
generator = new EqualInstallmentsAmortizationLoanScheduleGenerator();
|
||||
break;
|
||||
case EQUAL_INSTALLMENTS:
|
||||
generator = new EqualInstallmentsAmortizationLoanScheduleGenerator();
|
||||
break;
|
||||
case EQUAL_PRINCIPAL:
|
||||
generator = new EqualPrincipalAmortizationLoanScheduleGenerator();
|
||||
break;
|
||||
}
|
||||
|
||||
return generator;
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.MathContext;
|
||||
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Declining balance can be amortized (see {@link AmortizationMethod}) in two
|
||||
* ways at present:
|
||||
* <ol>
|
||||
* <li>Equal principal payments</li>
|
||||
* <li>Equal installment payments</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* When amortized using <i>equal principal payments</i>, the <b>principal
|
||||
* component</b> of each installment is fixed and <b>interest due</b> is
|
||||
* calculated from the <b>outstanding principal balance</b> resulting in a
|
||||
* different <b>total payment due</b> for each installment.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* When amortized using <i>equal installments</i>, the <b>total payment due</b>
|
||||
* for each installment is fixed and is calculated using the excel like
|
||||
* <code>pmt</code> function. The <b>interest due</b> is calculated from the
|
||||
* <b>outstanding principal balance</b> which results in a <b>principal
|
||||
* component</b> that is <b>total payment due</b> minus <b>interest due</b>.
|
||||
* </p>
|
||||
*/
|
||||
public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator {
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
|
||||
@SuppressWarnings("unused") final Money totalCumulativeInterest,
|
||||
@SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
|
||||
final int daysInPeriod, final Money outstandingBalance,
|
||||
final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc) {
|
||||
|
||||
PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
|
||||
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestPaymentDueToGrace, daysInPeriod,
|
||||
outstandingBalance);
|
||||
|
||||
Money interestForThisInstallment = result.interest();
|
||||
|
||||
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, daysInPeriod,
|
||||
outstandingBalance, periodNumber, mc);
|
||||
|
||||
// update cumulative fields for principal & interest
|
||||
Money interestBroughtFowardDueToGrace = result.interestPaymentDueToGrace();
|
||||
Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment);
|
||||
|
||||
// adjust if needed
|
||||
principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
|
||||
totalCumulativePrincipalToDate, periodNumber);
|
||||
|
||||
return new PrincipalInterest(principalForThisInstallment, interestForThisInstallment, interestBroughtFowardDueToGrace);
|
||||
}
|
||||
}
|
||||
-74
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Declining balance can be amortized (see {@link AmortizationMethod}) in two ways
|
||||
* at present:
|
||||
* <ol>
|
||||
* <li>Equal principal payments</li>
|
||||
* <li>Equal installment payments</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* When amortized using <i>equal principal payments</i>, the <b>principal
|
||||
* component</b> of each installment is fixed and <b>interest due</b> is
|
||||
* calculated from the <b>outstanding principal balance</b> resulting in a
|
||||
* different <b>total payment due</b> for each installment.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* When amortized using <i>equal installments</i>, the <b>total payment due</b>
|
||||
* for each installment is fixed and is calculated using the excel like
|
||||
* <code>pmt</code> function. The <b>interest due</b> is calculated from the
|
||||
* <b>outstanding principal balance</b> which results in a <b>principal
|
||||
* component</b> that is <b>total payment due</b> minus <b>interest due</b>.
|
||||
* </p>
|
||||
*/
|
||||
public class DecliningBalanceMethodLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
|
||||
private final AmortizationLoanScheduleGeneratorFactory amortizationLoanScheduleGeneratorFactory = new AmortizationLoanScheduleGeneratorFactory();
|
||||
|
||||
@Override
|
||||
public LoanScheduleData generate(final ApplicationCurrency applicationCurrency, final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final Integer loanTermFrequency, final PeriodFrequencyType loanTermFrequencyType, final LocalDate disbursementDate,
|
||||
final LocalDate firstRepaymentDate, final LocalDate interestCalculatedFrom, final Set<LoanCharge> loanCharges) {
|
||||
|
||||
// 1. generate valid set of 'due dates' based on some of the 'loan
|
||||
// attributes'
|
||||
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate);
|
||||
|
||||
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate = this.scheduledDateGenerator
|
||||
.idealDisbursementDateBasedOnFirstRepaymentDate(loanScheduleInfo, scheduledDates);
|
||||
|
||||
// 2. determine the 'periodic' interest rate based on the 'repayment
|
||||
// periods' so we can use
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod = this.periodicInterestRateCalculator.calculateFrom(loanScheduleInfo);
|
||||
|
||||
// Determine with 'amortisation' approach to use
|
||||
final AmortizationLoanScheduleGenerator generator = this.amortizationLoanScheduleGeneratorFactory.createGenerator(loanScheduleInfo
|
||||
.getAmortizationMethod());
|
||||
|
||||
return generator.generate(applicationCurrency, loanScheduleInfo, disbursementDate, interestCalculatedFrom,
|
||||
periodInterestRateForRepaymentPeriod, idealDisbursementDateBasedOnFirstRepaymentDate, scheduledDates, loanCharges);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -18,10 +18,10 @@ public class DefaultLoanScheduleGeneratorFactory implements LoanScheduleGenerato
|
||||
|
||||
switch (interestMethod) {
|
||||
case FLAT:
|
||||
loanScheduleGenerator = new FlatMethodLoanScheduleGenerator();
|
||||
loanScheduleGenerator = new FlatInterestLoanScheduleGenerator();
|
||||
break;
|
||||
case DECLINING_BALANCE:
|
||||
loanScheduleGenerator = new DecliningBalanceMethodLoanScheduleGenerator();
|
||||
loanScheduleGenerator = new DecliningBalanceInterestLoanScheduleGenerator();
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
|
||||
+69
-87
@@ -5,98 +5,80 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.domain.LocalDateInterval;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public class DefaultPaymentPeriodsInOneYearCalculator implements
|
||||
PaymentPeriodsInOneYearCalculator {
|
||||
public class DefaultPaymentPeriodsInOneYearCalculator implements PaymentPeriodsInOneYearCalculator {
|
||||
|
||||
@Override
|
||||
public Integer calculate(final PeriodFrequencyType repaymentFrequencyType) {
|
||||
@Override
|
||||
public Integer calculate(final PeriodFrequencyType repaymentFrequencyType) {
|
||||
|
||||
Integer paymentPeriodsInOneYear = Integer.valueOf(0);
|
||||
switch (repaymentFrequencyType) {
|
||||
case DAYS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(365);
|
||||
break;
|
||||
case WEEKS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(52);
|
||||
break;
|
||||
case MONTHS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(12);
|
||||
break;
|
||||
case YEARS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(1);
|
||||
break;
|
||||
case INVALID:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(0);
|
||||
break;
|
||||
}
|
||||
return paymentPeriodsInOneYear;
|
||||
}
|
||||
Integer paymentPeriodsInOneYear = Integer.valueOf(0);
|
||||
switch (repaymentFrequencyType) {
|
||||
case DAYS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(365);
|
||||
break;
|
||||
case WEEKS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(52);
|
||||
break;
|
||||
case MONTHS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(12);
|
||||
break;
|
||||
case YEARS:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(1);
|
||||
break;
|
||||
case INVALID:
|
||||
paymentPeriodsInOneYear = Integer.valueOf(0);
|
||||
break;
|
||||
}
|
||||
return paymentPeriodsInOneYear;
|
||||
}
|
||||
|
||||
/**
|
||||
* calculates the number of grace repayment periods and can be a fraction of a repayment period.
|
||||
*/
|
||||
@Override
|
||||
public double calculateRepaymentPeriodAsAFractionOfDays(
|
||||
PeriodFrequencyType repaymentPeriodFrequencyType,
|
||||
Integer every, LocalDate interestCalculatedFrom,
|
||||
List<LocalDate> scheduledDates, LocalDate disbursementDate) {
|
||||
|
||||
Double periodFraction = Double.valueOf("0");
|
||||
|
||||
if (interestCalculatedFrom != null && !interestCalculatedFrom.isBefore(disbursementDate)) {
|
||||
Integer repaymentPeriodIndex = 0;
|
||||
LocalDate lastRepaymentPeriod = disbursementDate;
|
||||
for (LocalDate repaymentPeriodDueDate : scheduledDates) {
|
||||
if (interestCalculatedFrom.isBefore(repaymentPeriodDueDate)) {
|
||||
// fraction is
|
||||
int numberOfDaysInterestCalculationGraceInPeriod = Days
|
||||
.daysBetween(
|
||||
lastRepaymentPeriod.toDateMidnight()
|
||||
.toDateTime(),
|
||||
interestCalculatedFrom.toDateMidnight()
|
||||
.toDateTime()).getDays();
|
||||
periodFraction = calculateRepaymentPeriodFraction(
|
||||
repaymentPeriodFrequencyType, every,
|
||||
numberOfDaysInterestCalculationGraceInPeriod);
|
||||
periodFraction = periodFraction
|
||||
+ repaymentPeriodIndex.doubleValue();
|
||||
break;
|
||||
}
|
||||
repaymentPeriodIndex++;
|
||||
lastRepaymentPeriod = repaymentPeriodDueDate;
|
||||
}
|
||||
}
|
||||
|
||||
return periodFraction;
|
||||
}
|
||||
|
||||
private double calculateRepaymentPeriodFraction(PeriodFrequencyType repaymentPeriodFrequencyType, Integer every, Integer numberOfDaysInterestCalculationGrace) {
|
||||
|
||||
Double fraction = Double.valueOf("0");
|
||||
switch (repaymentPeriodFrequencyType) {
|
||||
case DAYS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() * every.doubleValue();
|
||||
break;
|
||||
case WEEKS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("7.0") * every.doubleValue());
|
||||
break;
|
||||
case MONTHS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("30.0") * every.doubleValue());
|
||||
break;
|
||||
case YEARS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("365.0") * every.doubleValue());
|
||||
break;
|
||||
case INVALID:
|
||||
fraction = Double.valueOf("0");
|
||||
break;
|
||||
}
|
||||
return fraction;
|
||||
}
|
||||
@Override
|
||||
public double calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate,
|
||||
final LocalDate scheduledDueDate, final LocalDate interestChargedFromLocalDate,
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer repaidEvery) {
|
||||
|
||||
}
|
||||
Double periodFraction = Double.valueOf("0.0");
|
||||
|
||||
LocalDateInterval repaymentPeriod = new LocalDateInterval(repaymentPeriodStartDate, scheduledDueDate);
|
||||
|
||||
if (interestChargedFromLocalDate != null && repaymentPeriod.fallsBefore(interestChargedFromLocalDate.plusDays(1))) {
|
||||
periodFraction = Double.valueOf("1.0");
|
||||
} else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) {
|
||||
|
||||
int numberOfDaysInterestCalculationGraceInPeriod = Days.daysBetween(repaymentPeriodStartDate, interestChargedFromLocalDate)
|
||||
.getDays();
|
||||
periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery,
|
||||
numberOfDaysInterestCalculationGraceInPeriod);
|
||||
}
|
||||
|
||||
return periodFraction;
|
||||
}
|
||||
|
||||
private double calculateRepaymentPeriodFraction(final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer every,
|
||||
final Integer numberOfDaysInterestCalculationGrace) {
|
||||
|
||||
Double fraction = Double.valueOf("0");
|
||||
switch (repaymentPeriodFrequencyType) {
|
||||
case DAYS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() * every.doubleValue();
|
||||
break;
|
||||
case WEEKS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("7.0") * every.doubleValue());
|
||||
break;
|
||||
case MONTHS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("30.0") * every.doubleValue());
|
||||
break;
|
||||
case YEARS:
|
||||
fraction = numberOfDaysInterestCalculationGrace.doubleValue() / (Double.valueOf("365.0") * every.doubleValue());
|
||||
break;
|
||||
case INVALID:
|
||||
fraction = Double.valueOf("0");
|
||||
break;
|
||||
}
|
||||
return fraction;
|
||||
}
|
||||
}
|
||||
+59
-62
@@ -9,46 +9,47 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
|
||||
@Override
|
||||
public List<LocalDate> generate(final LoanProductMinimumRepaymentScheduleRelatedDetail loanScheduleDateInfo, final LocalDate disbursementDate, final LocalDate firstRepaymentPeriodDate) {
|
||||
public List<LocalDate> generate(final LoanApplicationTerms loanApplicationTerms) {
|
||||
|
||||
List<LocalDate> dueRepaymentPeriodDates = new ArrayList<LocalDate>(loanScheduleDateInfo.getNumberOfRepayments());
|
||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||
|
||||
LocalDate startDate = disbursementDate;
|
||||
final List<LocalDate> dueRepaymentPeriodDates = new ArrayList<LocalDate>(numberOfRepayments);
|
||||
|
||||
for (int repaymentPeriod = 1; repaymentPeriod <= loanScheduleDateInfo.getNumberOfRepayments(); repaymentPeriod++) {
|
||||
LocalDate startDate = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getRepaymentsStartingFromLocalDate();
|
||||
|
||||
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
|
||||
|
||||
LocalDate dueRepaymentPeriodDate = startDate;
|
||||
|
||||
if (repaymentPeriod == 1 && firstRepaymentPeriodDate != null) {
|
||||
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
|
||||
} else {
|
||||
switch (loanScheduleDateInfo.getRepaymentPeriodFrequencyType()) {
|
||||
case DAYS:
|
||||
dueRepaymentPeriodDate = startDate.plusDays(loanScheduleDateInfo
|
||||
.getRepayEvery());
|
||||
break;
|
||||
case WEEKS:
|
||||
dueRepaymentPeriodDate = startDate.plusWeeks(loanScheduleDateInfo
|
||||
.getRepayEvery());
|
||||
break;
|
||||
case MONTHS:
|
||||
dueRepaymentPeriodDate = startDate.plusMonths(loanScheduleDateInfo
|
||||
.getRepayEvery());
|
||||
break;
|
||||
case YEARS:
|
||||
dueRepaymentPeriodDate = startDate.plusYears(loanScheduleDateInfo
|
||||
.getRepayEvery());
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (repaymentPeriod == 1 && firstRepaymentPeriodDate != null) {
|
||||
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
|
||||
} else {
|
||||
|
||||
final int repaidEvery = loanApplicationTerms.getRepaymentEvery();
|
||||
|
||||
switch (loanApplicationTerms.getRepaymentPeriodFrequencyType()) {
|
||||
case DAYS:
|
||||
dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
|
||||
break;
|
||||
case WEEKS:
|
||||
dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
|
||||
break;
|
||||
case MONTHS:
|
||||
dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
|
||||
break;
|
||||
case YEARS:
|
||||
dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dueRepaymentPeriodDates.add(dueRepaymentPeriodDate);
|
||||
startDate = dueRepaymentPeriodDate;
|
||||
@@ -57,35 +58,31 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
return dueRepaymentPeriodDates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(
|
||||
LoanProductRelatedDetail loanScheduleDateInfo,
|
||||
List<LocalDate> scheduledDates) {
|
||||
|
||||
LocalDate firstRepaymentDate = scheduledDates.get(0);
|
||||
|
||||
LocalDate idealDisbursementDate = null;
|
||||
|
||||
switch (loanScheduleDateInfo.getRepaymentPeriodFrequencyType()) {
|
||||
case DAYS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusDays(loanScheduleDateInfo.getRepayEvery());
|
||||
break;
|
||||
case WEEKS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusWeeks(loanScheduleDateInfo
|
||||
.getRepayEvery());
|
||||
break;
|
||||
case MONTHS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusMonths(loanScheduleDateInfo
|
||||
.getRepayEvery());
|
||||
break;
|
||||
case YEARS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusYears(loanScheduleDateInfo
|
||||
.getRepayEvery());
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return idealDisbursementDate;
|
||||
}
|
||||
@Override
|
||||
public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType,
|
||||
final int repaidEvery, final List<LocalDate> scheduledDates) {
|
||||
|
||||
LocalDate firstRepaymentDate = scheduledDates.get(0);
|
||||
|
||||
LocalDate idealDisbursementDate = null;
|
||||
|
||||
switch (repaymentPeriodFrequencyType) {
|
||||
case DAYS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusDays(repaidEvery);
|
||||
break;
|
||||
case WEEKS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusWeeks(repaidEvery);
|
||||
break;
|
||||
case MONTHS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusMonths(repaidEvery);
|
||||
break;
|
||||
case YEARS:
|
||||
idealDisbursementDate = firstRepaymentDate.minusYears(repaidEvery);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return idealDisbursementDate;
|
||||
}
|
||||
}
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
|
||||
public class EqualInstallmentsAmortizationLoanScheduleGenerator implements AmortizationLoanScheduleGenerator {
|
||||
|
||||
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
private final PmtCalculator pmtCalculator = new PmtCalculator();
|
||||
|
||||
@Override
|
||||
public LoanScheduleData generate(final ApplicationCurrency currency, final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final LocalDate disbursementDate, final LocalDate interestCalculatedFrom,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod, final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
|
||||
final List<LocalDate> scheduledDates, final Set<LoanCharge> loanCharges) {
|
||||
|
||||
final Collection<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
|
||||
|
||||
// determine 'total payment' for each repayment based on pmt function
|
||||
// (and hence the total due overall)
|
||||
final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency();
|
||||
final Money totalDuePerInstallment = this.pmtCalculator.calculatePaymentForOnePeriodFrom(loanScheduleInfo,
|
||||
periodInterestRateForRepaymentPeriod, monetaryCurrency);
|
||||
final Money totalRepaymentDueForLoanTerm = this.pmtCalculator.calculateTotalRepaymentFrom(loanScheduleInfo,
|
||||
periodInterestRateForRepaymentPeriod, monetaryCurrency);
|
||||
|
||||
Money totalInterestDue = totalRepaymentDueForLoanTerm.minus(loanScheduleInfo.getPrincipal());
|
||||
Money outstandingBalance = loanScheduleInfo.getPrincipal();
|
||||
Money principalDisbursed = loanScheduleInfo.getPrincipal();
|
||||
Money totalPrincipal = Money.zero(monetaryCurrency);
|
||||
Money totalInterest = Money.zero(monetaryCurrency);
|
||||
|
||||
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
|
||||
.calculateRepaymentPeriodAsAFractionOfDays(loanScheduleInfo.getRepaymentPeriodFrequencyType(),
|
||||
loanScheduleInfo.getRepayEvery(), interestCalculatedFrom, scheduledDates,
|
||||
idealDisbursementDateBasedOnFirstRepaymentDate);
|
||||
|
||||
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueAtDisbursement()) {
|
||||
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
// create entries of disbursement period on loan schedule
|
||||
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(disbursementDate,
|
||||
principalDisbursed.getAmount(), chargesDueAtTimeOfDisbursement, false);
|
||||
periods.add(disbursementPeriod);
|
||||
|
||||
int loanTermInDays = Integer.valueOf(0);
|
||||
BigDecimal totalPrincipalDisbursed = principalDisbursed.getAmount();
|
||||
BigDecimal totalPrincipalExpected = BigDecimal.ZERO;
|
||||
BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
|
||||
BigDecimal totalInterestCharged = BigDecimal.ZERO;
|
||||
BigDecimal totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
|
||||
BigDecimal totalPenaltyChargesCharged = BigDecimal.ZERO;
|
||||
BigDecimal totalWaived = BigDecimal.ZERO;
|
||||
BigDecimal totalWrittenOff = BigDecimal.ZERO;
|
||||
BigDecimal totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
|
||||
BigDecimal totalRepayment = BigDecimal.ZERO;
|
||||
BigDecimal totalOutstanding = BigDecimal.ZERO;
|
||||
|
||||
LocalDate startDate = disbursementDate;
|
||||
int periodNumber = 1;
|
||||
for (LocalDate scheduledDueDate : scheduledDates) {
|
||||
|
||||
// number of days from startDate to this scheduledDate
|
||||
int daysInPeriod = Days.daysBetween(startDate, scheduledDueDate).getDays();
|
||||
|
||||
Money interestForInstallment = this.periodicInterestRateCalculator.calculateInterestOn(outstandingBalance,
|
||||
periodInterestRateForRepaymentPeriod, daysInPeriod, loanScheduleInfo);
|
||||
Money principalForInstallment = this.periodicInterestRateCalculator.calculatePrincipalOn(totalDuePerInstallment,
|
||||
interestForInstallment, loanScheduleInfo);
|
||||
|
||||
if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
|
||||
Money graceOnInterestForRepaymentPeriod = interestForInstallment;
|
||||
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
|
||||
totalInterestDue = totalInterestDue.minus(graceOnInterestForRepaymentPeriod);
|
||||
interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction
|
||||
- Integer.valueOf(1).doubleValue();
|
||||
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25")
|
||||
&& interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
|
||||
Money graceOnInterestForRepaymentPeriod = interestForInstallment
|
||||
.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
|
||||
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
|
||||
totalInterestDue = totalInterestDue.minus(graceOnInterestForRepaymentPeriod);
|
||||
interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf("0");
|
||||
}
|
||||
|
||||
totalPrincipal = totalPrincipal.plus(principalForInstallment);
|
||||
totalInterest = totalInterest.plus(interestForInstallment);
|
||||
|
||||
if (periodNumber == loanScheduleInfo.getNumberOfRepayments()) {
|
||||
Money principalDifference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
|
||||
if (principalDifference.isLessThanZero()) {
|
||||
principalForInstallment = principalForInstallment.plus(principalDifference.abs());
|
||||
} else if (principalDifference.isGreaterThanZero()) {
|
||||
principalForInstallment = principalForInstallment.minus(principalDifference.abs());
|
||||
}
|
||||
|
||||
final Money interestDifference = totalInterest.minus(totalInterestDue);
|
||||
if (interestDifference.isLessThanZero()) {
|
||||
interestForInstallment = interestForInstallment.plus(interestDifference.abs());
|
||||
} else if (interestDifference.isGreaterThanZero()) {
|
||||
interestForInstallment = interestForInstallment.minus(interestDifference.abs());
|
||||
}
|
||||
}
|
||||
|
||||
outstandingBalance = outstandingBalance.minus(principalForInstallment);
|
||||
|
||||
final Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency);
|
||||
final Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(startDate, scheduledDueDate, loanCharges,
|
||||
monetaryCurrency);
|
||||
final Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment).plus(feeChargesForInstallment)
|
||||
.plus(penaltyChargesForInstallment);
|
||||
|
||||
LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate, scheduledDueDate,
|
||||
principalForInstallment.getAmount(), outstandingBalance.getAmount(), interestForInstallment.getAmount(),
|
||||
feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount(), totalInstallmentDue.getAmount());
|
||||
|
||||
periods.add(installment);
|
||||
|
||||
// handle cumulative fields
|
||||
loanTermInDays += daysInPeriod;
|
||||
totalPrincipalExpected = totalPrincipalExpected.add(principalForInstallment.getAmount());
|
||||
totalInterestCharged = totalInterestCharged.add(interestForInstallment.getAmount());
|
||||
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
|
||||
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());
|
||||
totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount());
|
||||
startDate = scheduledDueDate;
|
||||
|
||||
periodNumber++;
|
||||
}
|
||||
|
||||
final CurrencyData currencyData = new CurrencyData(currency.getCode(), currency.getName(),
|
||||
monetaryCurrency.getDigitsAfterDecimal(), currency.getDisplaySymbol(), currency.getNameCode());
|
||||
|
||||
return new LoanScheduleData(currencyData, periods, loanTermInDays, totalPrincipalDisbursed, totalPrincipalExpected,
|
||||
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalWaived, totalWrittenOff,
|
||||
totalRepaymentExpected, totalRepayment, totalOutstanding);
|
||||
}
|
||||
|
||||
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
|
||||
final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isFeeCharge()) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
|
||||
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
return cumulative;
|
||||
}
|
||||
}
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
|
||||
public class EqualPrincipalAmortizationLoanScheduleGenerator implements AmortizationLoanScheduleGenerator {
|
||||
|
||||
private final PeriodicInterestRateCalculator periodicInterestRateCalculator = new PeriodicInterestRateCalculator();
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
@Override
|
||||
public LoanScheduleData generate(final ApplicationCurrency currency, final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final LocalDate disbursementDate, final LocalDate interestCalculatedFrom,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod, final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
|
||||
final List<LocalDate> scheduledDates, final Set<LoanCharge> loanCharges) {
|
||||
|
||||
final Collection<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
|
||||
|
||||
// 1. we know 'principal' must be equal over repayments so use this to
|
||||
// calculate interest and thus total repayment due.
|
||||
final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency();
|
||||
Money totalDuePerInstallment = Money.zero(monetaryCurrency);
|
||||
|
||||
Money outstandingBalance = loanScheduleInfo.getPrincipal();
|
||||
Money principalDisbursed = loanScheduleInfo.getPrincipal();
|
||||
Money totalPrincipal = Money.zero(monetaryCurrency);
|
||||
Money totalInterest = Money.zero(monetaryCurrency);
|
||||
|
||||
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
|
||||
.calculateRepaymentPeriodAsAFractionOfDays(loanScheduleInfo.getRepaymentPeriodFrequencyType(),
|
||||
loanScheduleInfo.getRepayEvery(), interestCalculatedFrom, scheduledDates,
|
||||
idealDisbursementDateBasedOnFirstRepaymentDate);
|
||||
|
||||
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueAtDisbursement()) {
|
||||
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
// create entries of disbursement period on loan schedule
|
||||
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(disbursementDate,
|
||||
principalDisbursed.getAmount(), chargesDueAtTimeOfDisbursement, false);
|
||||
periods.add(disbursementPeriod);
|
||||
|
||||
int loanTermInDays = Integer.valueOf(0);
|
||||
BigDecimal totalPrincipalDisbursed = principalDisbursed.getAmount();
|
||||
BigDecimal totalPrincipalExpected = BigDecimal.ZERO;
|
||||
BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
|
||||
BigDecimal totalInterestCharged = BigDecimal.ZERO;
|
||||
BigDecimal totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
|
||||
BigDecimal totalPenaltyChargesCharged = BigDecimal.ZERO;
|
||||
BigDecimal totalWaived = BigDecimal.ZERO;
|
||||
BigDecimal totalWrittenOff = BigDecimal.ZERO;
|
||||
BigDecimal totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
|
||||
BigDecimal totalRepayment = BigDecimal.ZERO;
|
||||
BigDecimal totalOutstanding = BigDecimal.ZERO;
|
||||
|
||||
LocalDate startDate = disbursementDate;
|
||||
int periodNumber = 1;
|
||||
for (LocalDate scheduledDueDate : scheduledDates) {
|
||||
|
||||
// number of days from startDate to this scheduledDate
|
||||
int daysInPeriod = Days.daysBetween(startDate.toDateMidnight().toDateTime(), scheduledDueDate.toDateMidnight().toDateTime())
|
||||
.getDays();
|
||||
|
||||
Money interestForInstallment = this.periodicInterestRateCalculator.calculateInterestOn(outstandingBalance,
|
||||
periodInterestRateForRepaymentPeriod, daysInPeriod, loanScheduleInfo);
|
||||
Money principalForInstallment = this.periodicInterestRateCalculator.calculatePrincipalOn(totalDuePerInstallment,
|
||||
interestForInstallment, loanScheduleInfo);
|
||||
|
||||
if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
|
||||
Money graceOnInterestForRepaymentPeriod = interestForInstallment;
|
||||
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
|
||||
interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction
|
||||
- Integer.valueOf(1).doubleValue();
|
||||
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25")
|
||||
&& interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
|
||||
Money graceOnInterestForRepaymentPeriod = interestForInstallment
|
||||
.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
|
||||
interestForInstallment = interestForInstallment.minus(graceOnInterestForRepaymentPeriod);
|
||||
interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf("0");
|
||||
}
|
||||
|
||||
totalPrincipal = totalPrincipal.plus(principalForInstallment);
|
||||
totalInterest = totalInterest.plus(interestForInstallment);
|
||||
|
||||
if (periodNumber == loanScheduleInfo.getNumberOfRepayments()) {
|
||||
Money principalDifference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
|
||||
if (principalDifference.isLessThanZero()) {
|
||||
principalForInstallment = principalForInstallment.plus(principalDifference.abs());
|
||||
} else if (principalDifference.isGreaterThanZero()) {
|
||||
principalForInstallment = principalForInstallment.minus(principalDifference.abs());
|
||||
}
|
||||
}
|
||||
|
||||
outstandingBalance = outstandingBalance.minus(principalForInstallment);
|
||||
|
||||
final Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency);
|
||||
final Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(startDate, scheduledDueDate, loanCharges,
|
||||
monetaryCurrency);
|
||||
|
||||
final Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment).plus(feeChargesForInstallment)
|
||||
.plus(penaltyChargesForInstallment);
|
||||
|
||||
LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate, scheduledDueDate,
|
||||
principalForInstallment.getAmount(), outstandingBalance.getAmount(), interestForInstallment.getAmount(),
|
||||
feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount(), totalInstallmentDue.getAmount());
|
||||
|
||||
periods.add(installment);
|
||||
|
||||
// handle cumulative fields
|
||||
loanTermInDays += daysInPeriod;
|
||||
totalPrincipalExpected = totalPrincipalExpected.add(principalForInstallment.getAmount());
|
||||
totalInterestCharged = totalInterestCharged.add(interestForInstallment.getAmount());
|
||||
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
|
||||
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());
|
||||
totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount());
|
||||
startDate = scheduledDueDate;
|
||||
|
||||
periodNumber++;
|
||||
}
|
||||
|
||||
final CurrencyData currencyData = new CurrencyData(currency.getCode(), currency.getName(),
|
||||
monetaryCurrency.getDigitsAfterDecimal(), currency.getDisplaySymbol(), currency.getNameCode());
|
||||
|
||||
return new LoanScheduleData(currencyData, periods, loanTermInDays, totalPrincipalDisbursed, totalPrincipalExpected,
|
||||
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalWaived, totalWrittenOff,
|
||||
totalRepaymentExpected, totalRepayment, totalOutstanding);
|
||||
}
|
||||
|
||||
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
|
||||
final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isFeeCharge()) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
|
||||
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
return cumulative;
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
|
||||
public class FinanicalFunctions {
|
||||
|
||||
@Deprecated
|
||||
public Money calculatePaymentForOnePeriodFrom(final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod, final MonetaryCurrency monetaryCurrency) {
|
||||
double interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
|
||||
|
||||
double futureValue = 0;
|
||||
double numberOfPeriods = loanScheduleInfo.getNumberOfRepayments().doubleValue();
|
||||
double principal = loanScheduleInfo.getPrincipal().getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
|
||||
// work out the total payment (principal + interest components) for each
|
||||
// installment due.
|
||||
// use the period type and interest as expressed e.g. 2% per month or
|
||||
// 24% per year even is 'daily' interest calculation is selected
|
||||
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principal, futureValue, false);
|
||||
|
||||
return Money.of(monetaryCurrency, BigDecimal.valueOf(paymentPerInstallment));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Money calculateTotalRepaymentFrom(final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod, final MonetaryCurrency monetaryCurrency) {
|
||||
double interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
|
||||
|
||||
double futureValue = 0;
|
||||
double numberOfPeriods = loanScheduleInfo.getNumberOfRepayments().doubleValue();
|
||||
double principal = loanScheduleInfo.getPrincipal().getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
|
||||
// work out the total payment (principal + interest components) for each
|
||||
// installment due.
|
||||
// use the period type and interest as expressed e.g. 2% per month or
|
||||
// 24% per year even is 'daily' interest calculation is selected
|
||||
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principal, futureValue, false);
|
||||
|
||||
double totalRepayment = paymentPerInstallment * loanScheduleInfo.getNumberOfRepayments();
|
||||
|
||||
return Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment));
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Money calculateTotalRepaymentFrom(final Money principal, final Integer loanTermFrequency,
|
||||
final BigDecimal periodInterestRateForLoanTermPeriod, final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
double interestRateFraction = periodInterestRateForLoanTermPeriod.doubleValue();
|
||||
|
||||
double futureValue = 0;
|
||||
double numberOfPeriods = loanTermFrequency.doubleValue();
|
||||
double principalAmount = principal.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
|
||||
// work out the total payment (principal + interest components) for each
|
||||
// installment due.
|
||||
// use the period type and interest as expressed e.g. 2% per month or
|
||||
// 24% per year even is 'daily' interest calculation is selected
|
||||
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principalAmount, futureValue, false);
|
||||
|
||||
double totalRepayment = paymentPerInstallment * loanTermFrequency;
|
||||
|
||||
return Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment));
|
||||
}
|
||||
|
||||
/**
|
||||
* PMT calculates a fixed monthly payment to be paid by borrower every
|
||||
* 'period' to ensure loan is paid off in full (with interest).
|
||||
*
|
||||
* This monthly payment c depends upon the monthly interest rate r
|
||||
* (expressed as a fraction, not a percentage, i.e., divide the quoted
|
||||
* yearly percentage rate by 100 and by 12 to obtain the monthly interest
|
||||
* rate), the number of monthly payments N called the loan's term, and the
|
||||
* amount borrowed P known as the loan's principal; c is given by the
|
||||
* formula:
|
||||
*
|
||||
* c = (r / (1 - (1 + r)^-N))P
|
||||
*
|
||||
* @param interestRateFraction
|
||||
* @param numberOfPayments
|
||||
* @param principal
|
||||
* @param futureValue
|
||||
* @param type
|
||||
*/
|
||||
public static double pmt(final double interestRateFraction, final double numberOfPayments, final double principal,
|
||||
final double futureValue, final boolean type) {
|
||||
double payment = 0;
|
||||
if (interestRateFraction == 0) {
|
||||
payment = -1 * (futureValue + principal) / numberOfPayments;
|
||||
} else {
|
||||
double r1 = interestRateFraction + 1;
|
||||
payment = (futureValue + principal * Math.pow(r1, numberOfPayments)) * interestRateFraction
|
||||
/ ((type ? r1 : 1) * (1 - Math.pow(r1, numberOfPayments)));
|
||||
}
|
||||
return payment;
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.MathContext;
|
||||
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
|
||||
public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator {
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal,
|
||||
final Money totalCumulativeInterest, final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
|
||||
final int daysInPeriod, final Money outstandingBalance,
|
||||
final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc) {
|
||||
|
||||
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, daysInPeriod,
|
||||
outstandingBalance, periodNumber, mc);
|
||||
|
||||
PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator,
|
||||
interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestPaymentDueToGrace, daysInPeriod,
|
||||
outstandingBalance);
|
||||
Money interestForThisInstallment = result.interest();
|
||||
|
||||
// update cumulative fields for principal & interest
|
||||
Money interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace();
|
||||
Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment);
|
||||
Money totalCumulativeInterestToDate = totalCumulativeInterest.plus(interestForThisInstallment);
|
||||
|
||||
// adjust if needed
|
||||
principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod(principalForThisInstallment,
|
||||
totalCumulativePrincipalToDate, periodNumber);
|
||||
|
||||
interestForThisInstallment = loanApplicationTerms.adjustInterestIfLastRepaymentPeriod(interestForThisInstallment,
|
||||
totalCumulativeInterestToDate, totalInterestDueForLoan, periodNumber);
|
||||
|
||||
return new PrincipalInterest(principalForThisInstallment, interestForThisInstallment, interestBroughtForwardDueToGrace);
|
||||
}
|
||||
}
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.Days;
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
@Override
|
||||
public LoanScheduleData generate(final ApplicationCurrency currency, final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final Integer loanTermFrequency, final PeriodFrequencyType loanTermFrequencyType, final LocalDate disbursementDate,
|
||||
final LocalDate firstRepaymentDate, final LocalDate interestCalculatedFrom, final Set<LoanCharge> loanCharges) {
|
||||
|
||||
final Collection<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
|
||||
|
||||
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanScheduleInfo, disbursementDate, firstRepaymentDate);
|
||||
|
||||
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
|
||||
|
||||
BigDecimal loanTermPeriodsInYear = BigDecimal.valueOf(this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType));
|
||||
BigDecimal interestRateForLoanTerm = loanScheduleInfo.getAnnualNominalInterestRate().divide(loanTermPeriodsInYear, mc)
|
||||
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc).multiply(BigDecimal.valueOf(loanTermFrequency));
|
||||
|
||||
final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency();
|
||||
Money totalInterestForLoanTerm = loanScheduleInfo.getPrincipal().multiplyRetainScale(interestRateForLoanTerm,
|
||||
RoundingMode.HALF_EVEN);
|
||||
|
||||
Money interestPerInstallment = totalInterestForLoanTerm.dividedBy(Long.valueOf(loanScheduleInfo.getNumberOfRepayments()),
|
||||
RoundingMode.HALF_EVEN);
|
||||
|
||||
Money principalPerInstallment = loanScheduleInfo.getPrincipal().dividedBy(loanScheduleInfo.getNumberOfRepayments(),
|
||||
RoundingMode.HALF_EVEN);
|
||||
|
||||
Money outstandingBalance = loanScheduleInfo.getPrincipal();
|
||||
Money principalDisbursed = loanScheduleInfo.getPrincipal();
|
||||
Money totalPrincipal = Money.zero(outstandingBalance.getCurrency());
|
||||
Money totalInterest = Money.zero(outstandingBalance.getCurrency());
|
||||
|
||||
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueAtDisbursement()) {
|
||||
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
// create entries of disbursement period on loan schedule
|
||||
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(disbursementDate,
|
||||
principalDisbursed.getAmount(), chargesDueAtTimeOfDisbursement, false);
|
||||
periods.add(disbursementPeriod);
|
||||
|
||||
int loanTermInDays = Integer.valueOf(0);
|
||||
BigDecimal totalPrincipalDisbursed = principalDisbursed.getAmount();
|
||||
BigDecimal totalPrincipalExpected = BigDecimal.ZERO;
|
||||
BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
|
||||
BigDecimal totalInterestCharged = BigDecimal.ZERO;
|
||||
BigDecimal totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
|
||||
BigDecimal totalPenaltyChargesCharged = BigDecimal.ZERO;
|
||||
BigDecimal totalWaived = BigDecimal.ZERO;
|
||||
BigDecimal totalWrittenOff = BigDecimal.ZERO;
|
||||
BigDecimal totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
|
||||
BigDecimal totalRepayment = BigDecimal.ZERO;
|
||||
BigDecimal totalOutstanding = BigDecimal.ZERO;
|
||||
|
||||
LocalDate startDate = disbursementDate;
|
||||
int periodNumber = 1;
|
||||
for (LocalDate scheduledDueDate : scheduledDates) {
|
||||
totalPrincipal = totalPrincipal.plus(principalPerInstallment);
|
||||
totalInterest = totalInterest.plus(interestPerInstallment);
|
||||
|
||||
// number of days from startDate to this scheduledDate
|
||||
int daysInPeriod = Days.daysBetween(startDate.toDateMidnight().toDateTime(), scheduledDueDate.toDateMidnight().toDateTime())
|
||||
.getDays();
|
||||
|
||||
if (periodNumber == loanScheduleInfo.getNumberOfRepayments()) {
|
||||
final Money difference = totalPrincipal.minus(loanScheduleInfo.getPrincipal());
|
||||
if (difference.isLessThanZero()) {
|
||||
principalPerInstallment = principalPerInstallment.plus(difference.abs());
|
||||
} else if (difference.isGreaterThanZero()) {
|
||||
principalPerInstallment = principalPerInstallment.minus(difference.abs());
|
||||
}
|
||||
|
||||
final Money interestDifference = totalInterest.minus(totalInterestForLoanTerm);
|
||||
if (interestDifference.isLessThanZero()) {
|
||||
interestPerInstallment = interestPerInstallment.plus(interestDifference.abs());
|
||||
} else if (interestDifference.isGreaterThanZero()) {
|
||||
interestPerInstallment = interestPerInstallment.minus(interestDifference.abs());
|
||||
}
|
||||
}
|
||||
|
||||
outstandingBalance = outstandingBalance.minus(principalPerInstallment);
|
||||
|
||||
final Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency);
|
||||
final Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(startDate, scheduledDueDate, loanCharges,
|
||||
monetaryCurrency);
|
||||
|
||||
final Money totalInstallmentDue = principalPerInstallment //
|
||||
.plus(interestPerInstallment) //
|
||||
.plus(feeChargesForInstallment) //
|
||||
.plus(penaltyChargesForInstallment);
|
||||
|
||||
LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate, scheduledDueDate,
|
||||
principalPerInstallment.getAmount(), outstandingBalance.getAmount(), interestPerInstallment.getAmount(),
|
||||
feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount(), totalInstallmentDue.getAmount());
|
||||
|
||||
periods.add(installment);
|
||||
|
||||
// handle cumulative fields
|
||||
loanTermInDays += daysInPeriod;
|
||||
totalPrincipalExpected = totalPrincipalExpected.add(principalPerInstallment.getAmount());
|
||||
totalInterestCharged = totalInterestCharged.add(interestPerInstallment.getAmount());
|
||||
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
|
||||
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());
|
||||
totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount());
|
||||
startDate = scheduledDueDate;
|
||||
|
||||
periodNumber++;
|
||||
}
|
||||
|
||||
final CurrencyData currencyData = new CurrencyData(currency.getCode(), currency.getName(),
|
||||
monetaryCurrency.getDigitsAfterDecimal(), currency.getDisplaySymbol(), currency.getNameCode());
|
||||
|
||||
return new LoanScheduleData(currencyData, periods, loanTermInDays, totalPrincipalDisbursed, totalPrincipalExpected,
|
||||
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalWaived, totalWrittenOff,
|
||||
totalRepaymentExpected, totalRepayment, totalOutstanding);
|
||||
}
|
||||
|
||||
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
|
||||
final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isFeeCharge()) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
|
||||
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
|
||||
for (LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) && loanCharge.isPenaltyCharge()) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
|
||||
return cumulative;
|
||||
}
|
||||
}
|
||||
+691
@@ -0,0 +1,691 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.util.Date;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
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.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public final class LoanApplicationTerms {
|
||||
|
||||
private final ApplicationCurrency currency;
|
||||
private final Integer loanTermFrequency;
|
||||
private final PeriodFrequencyType loanTermPeriodFrequencyType;
|
||||
private final Integer numberOfRepayments;
|
||||
private final Integer repaymentEvery;
|
||||
private final PeriodFrequencyType repaymentPeriodFrequencyType;
|
||||
private final AmortizationMethod amortizationMethod;
|
||||
|
||||
private final InterestMethod interestMethod;
|
||||
private final BigDecimal interestRatePerPeriod;
|
||||
private final PeriodFrequencyType interestRatePeriodFrequencyType;
|
||||
private final BigDecimal annualNominalInterestRate;
|
||||
private final InterestCalculationPeriodMethod interestCalculationPeriodMethod;
|
||||
|
||||
private final Money principal;
|
||||
private final LocalDate expectedDisbursementDate;
|
||||
private final LocalDate repaymentsStartingFromDate;
|
||||
|
||||
/**
|
||||
* Integer representing the number of 'repayment frequencies' or
|
||||
* installments where 'grace' should apply to the principal component of a
|
||||
* loans repayment period (installment).
|
||||
*/
|
||||
private final Integer principalGrace;
|
||||
|
||||
/**
|
||||
* Integer representing the number of 'repayment frequencies' or
|
||||
* installments where 'grace' should apply to the payment of interest in a
|
||||
* loans repayment period (installment).
|
||||
*
|
||||
* <b>Note:</b> Interest is still calculated taking into account the full
|
||||
* loan term, the interest is simply offset to a later period.
|
||||
*/
|
||||
private final Integer interestPaymentGrace;
|
||||
|
||||
/**
|
||||
* Integer representing the number of 'repayment frequencies' or
|
||||
* installments where 'grace' should apply to the charging of interest in a
|
||||
* loans repayment period (installment).
|
||||
*
|
||||
* <b>Note:</b> The loan is <i>interest-free</i> for the period of time
|
||||
* indicated.
|
||||
*/
|
||||
private final Integer interestChargingGrace;
|
||||
|
||||
/**
|
||||
* Legacy method of support 'grace' on the charging of interest on a loan.
|
||||
*
|
||||
* <p>
|
||||
* For the typical structured loan, its reasonable to use an integer to
|
||||
* indicate the number of 'repayment frequency' periods the 'grace' should
|
||||
* apply to but for slightly <b>irregular</b> loans where the period between
|
||||
* disbursement and the date of the 'first repayment period' isnt doest
|
||||
* match the 'repayment frequency' but can be less (15days instead of 1
|
||||
* month) or more (6 weeks instead of 1 month) - The idea was to use a date
|
||||
* to indicate from whence interest should be charged.
|
||||
* </p>
|
||||
*/
|
||||
private final LocalDate interestChargedFromDate;
|
||||
private final Money inArrearsTolerance;
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType, final AmortizationMethod amortizationMethod,
|
||||
final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod,
|
||||
final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate,
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Money principalMoney,
|
||||
final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, final Integer graceOnPrincipalPayment,
|
||||
final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate,
|
||||
final Money inArrearsTolerance) {
|
||||
|
||||
return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
|
||||
repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType,
|
||||
annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate,
|
||||
repaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
|
||||
interestChargedFromDate, inArrearsTolerance);
|
||||
}
|
||||
|
||||
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType, final LocalDate expectedDisbursementDate,
|
||||
final LocalDate repaymentsStartingFromDate, final Money inArrearsTolerance,
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail) {
|
||||
|
||||
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
|
||||
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
|
||||
final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod();
|
||||
final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod();
|
||||
final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod();
|
||||
final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType();
|
||||
final BigDecimal annualNominalInterestRate = loanProductRelatedDetail.getAnnualNominalInterestRate();
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail
|
||||
.getInterestCalculationPeriodMethod();
|
||||
final Money principalMoney = loanProductRelatedDetail.getPrincipal();
|
||||
|
||||
//
|
||||
final Integer graceOnPrincipalPayment = 0;
|
||||
final Integer graceOnInterestPayment = 0;
|
||||
final Integer graceOnInterestCharged = 0;
|
||||
final LocalDate interestChargedFromDate = null;
|
||||
|
||||
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
|
||||
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
|
||||
expectedDisbursementDate, repaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance);
|
||||
}
|
||||
|
||||
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType, final AmortizationMethod amortizationMethod,
|
||||
final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod,
|
||||
final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate,
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Money principal,
|
||||
final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, final Integer principalGrace,
|
||||
final Integer interestPaymentGrace, final Integer interestChargingGrace, final LocalDate interestChargedFromDate,
|
||||
final Money inArrearsTolerance) {
|
||||
this.currency = currency;
|
||||
this.loanTermFrequency = loanTermFrequency;
|
||||
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
|
||||
this.numberOfRepayments = numberOfRepayments;
|
||||
this.repaymentEvery = repaymentEvery;
|
||||
this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType;
|
||||
this.amortizationMethod = amortizationMethod;
|
||||
|
||||
this.interestMethod = interestMethod;
|
||||
this.interestRatePerPeriod = interestRatePerPeriod;
|
||||
this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType;
|
||||
this.annualNominalInterestRate = annualNominalInterestRate;
|
||||
this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
|
||||
|
||||
this.principal = principal;
|
||||
this.expectedDisbursementDate = expectedDisbursementDate;
|
||||
this.repaymentsStartingFromDate = repaymentsStartingFromDate;
|
||||
|
||||
this.principalGrace = principalGrace;
|
||||
this.interestPaymentGrace = interestPaymentGrace;
|
||||
this.interestChargingGrace = interestChargingGrace;
|
||||
this.interestChargedFromDate = interestChargedFromDate;
|
||||
|
||||
this.inArrearsTolerance = inArrearsTolerance;
|
||||
}
|
||||
|
||||
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
|
||||
final int periodNumber) {
|
||||
|
||||
Money adjusted = principalForPeriod;
|
||||
|
||||
// adjust cumulative fields for principal & interest if needed
|
||||
if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) {
|
||||
|
||||
final Money difference = totalCumulativePrincipalToDate.minus(this.principal);
|
||||
if (difference.isLessThanZero()) {
|
||||
adjusted = principalForPeriod.plus(difference.abs());
|
||||
} else if (difference.isGreaterThanZero()) {
|
||||
adjusted = principalForPeriod.minus(difference.abs());
|
||||
}
|
||||
}
|
||||
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
public Money adjustInterestIfLastRepaymentPeriod(final Money interestForThisPeriod, final Money totalCumulativeInterestToDate,
|
||||
final Money totalInterestDueForLoan, final int periodNumber) {
|
||||
|
||||
Money adjusted = interestForThisPeriod;
|
||||
|
||||
if (isLastRepaymentPeriod(this.numberOfRepayments, periodNumber)) {
|
||||
|
||||
final Money interestDifference = totalCumulativeInterestToDate.minus(totalInterestDueForLoan);
|
||||
if (interestDifference.isLessThanZero()) {
|
||||
adjusted = interestForThisPeriod.plus(interestDifference.abs());
|
||||
} else if (interestDifference.isGreaterThanZero()) {
|
||||
adjusted = interestForThisPeriod.minus(interestDifference.abs());
|
||||
}
|
||||
}
|
||||
|
||||
return adjusted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the total interest to be charged on loan taking into account
|
||||
* grace settings.
|
||||
*
|
||||
*/
|
||||
public Money calculateTotalInterestCharged(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
|
||||
|
||||
Money totalInterestCharged = this.principal.zero();
|
||||
|
||||
switch (this.interestMethod) {
|
||||
case FLAT:
|
||||
final Money totalInterestChargedForLoanTerm = calculateTotalInterestDueWithoutGrace(calculator, mc);
|
||||
|
||||
final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
|
||||
|
||||
final Money totalGraceOnInterestCharged = totalInterestPerInstallment.multiplyRetainScale(getInterestChargingGrace(),
|
||||
mc.getRoundingMode());
|
||||
|
||||
totalInterestCharged = totalInterestChargedForLoanTerm.minus(totalGraceOnInterestCharged);
|
||||
break;
|
||||
case DECLINING_BALANCE:
|
||||
final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc);
|
||||
|
||||
final Money totalRepaymentDueForLoanTerm = calculateTotalDueForEqualInstallmentLoan(periodicInterestRateForRepaymentPeriod);
|
||||
|
||||
totalInterestCharged = totalRepaymentDueForLoanTerm.minus(this.principal);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return totalInterestCharged;
|
||||
}
|
||||
|
||||
public Money calculateTotalPrincipalForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final int daysInPeriod,
|
||||
final Money outstandingBalance, final int periodNumber, final MathContext mc) {
|
||||
|
||||
Money principalForInstallment = this.principal.zero();
|
||||
|
||||
switch (this.interestMethod) {
|
||||
case FLAT:
|
||||
principalForInstallment = calculateTotalPrincipalPerPeriodWithoutGrace(mc);
|
||||
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
|
||||
principalForInstallment = Money.zero(principalForInstallment.getCurrency());
|
||||
}
|
||||
break;
|
||||
case DECLINING_BALANCE:
|
||||
switch (this.amortizationMethod) {
|
||||
case EQUAL_INSTALLMENTS:
|
||||
// equal installments
|
||||
int periodsElapsed = periodNumber - 1;
|
||||
final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc);
|
||||
|
||||
final Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(
|
||||
periodicInterestRateForRepaymentPeriod, outstandingBalance, periodsElapsed);
|
||||
|
||||
principalForInstallment = calculatePrincipalDueForInstallment(calculator, mc, periodNumber,
|
||||
totalPmtForThisInstallment, daysInPeriod, outstandingBalance);
|
||||
break;
|
||||
case EQUAL_PRINCIPAL:
|
||||
principalForInstallment = calculateEqualPrincipalDueForInstallment(mc, periodNumber);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return principalForInstallment;
|
||||
}
|
||||
|
||||
public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc,
|
||||
final Money cumulatingInterestPaymentDueToGrace, final int daysInPeriod, final Money outstandingBalance) {
|
||||
|
||||
Money interestForInstallment = this.principal.zero();
|
||||
Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy();
|
||||
|
||||
switch (this.interestMethod) {
|
||||
case FLAT:
|
||||
|
||||
switch (this.amortizationMethod) {
|
||||
case EQUAL_INSTALLMENTS:
|
||||
// average out outstanding interest over remaining
|
||||
// instalments where interest is applicable
|
||||
interestForInstallment = calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(calculator, periodNumber,
|
||||
mc);
|
||||
break;
|
||||
case EQUAL_PRINCIPAL:
|
||||
// interest follows time-value of money and is brought
|
||||
// forward to next applicable interest payment period
|
||||
PrincipalInterest result = calculateTotalFlatInterestForPeriod(calculator, periodNumber, mc,
|
||||
interestBroughtForwardDueToGrace);
|
||||
interestForInstallment = result.interest();
|
||||
interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace();
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DECLINING_BALANCE:
|
||||
|
||||
Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator,
|
||||
mc, daysInPeriod, outstandingBalance);
|
||||
|
||||
Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace(calculator,
|
||||
interestCalculationGraceOnRepaymentPeriodFraction, mc, daysInPeriod, outstandingBalance, periodNumber);
|
||||
|
||||
interestForInstallment = interestForThisInstallmentAfterGrace;
|
||||
if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) {
|
||||
interestForInstallment = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentAfterGrace);
|
||||
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero();
|
||||
} else if (isInterestFreeGracePeriod(periodNumber)) {
|
||||
interestForInstallment = interestForInstallment.zero();
|
||||
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
|
||||
interestForInstallment = interestForThisInstallmentAfterGrace;
|
||||
} else {
|
||||
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace);
|
||||
}
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return new PrincipalInterest(null, interestForInstallment, interestBroughtForwardDueToGrace);
|
||||
}
|
||||
|
||||
private final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) {
|
||||
return periodNumber == numberOfRepayments;
|
||||
}
|
||||
|
||||
private Money calculateTotalDueForEqualInstallmentLoan(final BigDecimal periodicInterestRate) {
|
||||
|
||||
final int periodsElapsed = 0;
|
||||
final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed);
|
||||
final double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments;
|
||||
|
||||
return Money.of(principal.getCurrency(), BigDecimal.valueOf(totalRepayment));
|
||||
}
|
||||
|
||||
/**
|
||||
* general method to calculate totalInterestDue discounting any grace
|
||||
* settings
|
||||
*/
|
||||
private Money calculateTotalInterestDueWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
|
||||
|
||||
Money totalInterestDue = this.principal.zero();
|
||||
|
||||
switch (this.interestMethod) {
|
||||
case FLAT:
|
||||
final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc);
|
||||
|
||||
totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode());
|
||||
break;
|
||||
case DECLINING_BALANCE:
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return totalInterestDue;
|
||||
}
|
||||
|
||||
private BigDecimal calculateFlatInterestRateForLoanTerm(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
|
||||
|
||||
final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0"));
|
||||
|
||||
final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
|
||||
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
|
||||
|
||||
final BigDecimal loanTermFrequencyBigDecimal = BigDecimal.valueOf(this.loanTermFrequency);
|
||||
|
||||
return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
|
||||
.multiply(loanTermFrequencyBigDecimal);
|
||||
}
|
||||
|
||||
private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
|
||||
|
||||
final Money totalInterestForLoanTerm = calculateTotalInterestDueWithoutGrace(calculator, mc);
|
||||
|
||||
return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.numberOfRepayments), mc.getRoundingMode());
|
||||
}
|
||||
|
||||
private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc) {
|
||||
final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment();
|
||||
return this.principal.dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode());
|
||||
}
|
||||
|
||||
private PrincipalInterest calculateTotalFlatInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final int periodNumber, final MathContext mc, final Money cumulatingInterestPaymentDueToGrace) {
|
||||
|
||||
Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy();
|
||||
|
||||
Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
|
||||
if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) {
|
||||
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForInstallment);
|
||||
interestForInstallment = interestForInstallment.zero();
|
||||
} else if (isInterestFreeGracePeriod(periodNumber)) {
|
||||
interestForInstallment = interestForInstallment.zero();
|
||||
} else if (isFirstPeriodAfterInterestPaymentGracePeriod(periodNumber)) {
|
||||
interestForInstallment = cumulatingInterestPaymentDueToGrace.plus(interestForInstallment);
|
||||
interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero();
|
||||
}
|
||||
|
||||
return new PrincipalInterest(null, interestForInstallment, interestBroughtForwardDueToGrace);
|
||||
}
|
||||
|
||||
/*
|
||||
* calculates the interest that should be due for a given scheduled loan
|
||||
* repayment period. It takes into account GRACE periods and calculates how
|
||||
* much interest is due per period by averaging the number of periods where
|
||||
* interest is due and should be paid against the total known interest that
|
||||
* is due without grace.
|
||||
*/
|
||||
private Money calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final int periodNumber, final MathContext mc) {
|
||||
|
||||
Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
|
||||
if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) {
|
||||
interestForInstallment = interestForInstallment.zero();
|
||||
} else if (isInterestFreeGracePeriod(periodNumber)) {
|
||||
interestForInstallment = interestForInstallment.zero();
|
||||
} else {
|
||||
|
||||
final Money totalInterestForLoanTerm = calculateTotalInterestDueWithoutGrace(calculator, mc);
|
||||
|
||||
Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc);
|
||||
|
||||
Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace());
|
||||
Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree);
|
||||
|
||||
Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.numberOfRepayments);
|
||||
|
||||
interestForInstallment = realTotalInterestForLoan
|
||||
.dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode());
|
||||
}
|
||||
|
||||
return interestForInstallment;
|
||||
}
|
||||
|
||||
private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) {
|
||||
|
||||
final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
|
||||
|
||||
final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0"));
|
||||
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
|
||||
|
||||
final BigDecimal loanTermFrequencyBigDecimal = BigDecimal.valueOf(this.repaymentEvery);
|
||||
|
||||
return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
|
||||
.multiply(loanTermFrequencyBigDecimal);
|
||||
}
|
||||
|
||||
private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) {
|
||||
return calculator.calculate(this.repaymentPeriodFrequencyType).longValue();
|
||||
}
|
||||
|
||||
private int calculateNumberOfRepaymentsWithPrincipalPayment() {
|
||||
return this.numberOfRepayments - getPrincipalGrace();
|
||||
}
|
||||
|
||||
private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(final Integer totalNumberOfRepaymentPeriods) {
|
||||
return totalNumberOfRepaymentPeriods - Math.max(getInterestChargingGrace(), getInterestPaymentGrace());
|
||||
}
|
||||
|
||||
private Integer calculateNumberOfPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods) {
|
||||
return totalNumberOfRepaymentPeriods - getPrincipalGrace();
|
||||
}
|
||||
|
||||
private boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) {
|
||||
return periodNumber > 0 && periodNumber <= getPrincipalGrace();
|
||||
}
|
||||
|
||||
private boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) {
|
||||
return periodNumber > 0 && periodNumber <= getInterestPaymentGrace();
|
||||
}
|
||||
|
||||
private boolean isFirstPeriodAfterInterestPaymentGracePeriod(final int periodNumber) {
|
||||
return periodNumber > 0 && periodNumber == getInterestPaymentGrace() + 1;
|
||||
}
|
||||
|
||||
private boolean isInterestFreeGracePeriod(final int periodNumber) {
|
||||
return periodNumber > 0 && periodNumber <= getInterestChargingGrace();
|
||||
}
|
||||
|
||||
private Integer getPrincipalGrace() {
|
||||
Integer graceOnPrincipalPayments = Integer.valueOf(0);
|
||||
if (this.principalGrace != null) {
|
||||
graceOnPrincipalPayments = this.principalGrace;
|
||||
}
|
||||
return graceOnPrincipalPayments;
|
||||
}
|
||||
|
||||
private Integer getInterestPaymentGrace() {
|
||||
Integer graceOnInterestPayments = Integer.valueOf(0);
|
||||
if (this.interestPaymentGrace != null) {
|
||||
graceOnInterestPayments = this.interestPaymentGrace;
|
||||
}
|
||||
return graceOnInterestPayments;
|
||||
}
|
||||
|
||||
private Integer getInterestChargingGrace() {
|
||||
Integer graceOnInterestCharged = Integer.valueOf(0);
|
||||
if (this.interestChargingGrace != null) {
|
||||
graceOnInterestCharged = this.interestChargingGrace;
|
||||
}
|
||||
return graceOnInterestCharged;
|
||||
}
|
||||
|
||||
private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) {
|
||||
final double futureValue = 0;
|
||||
final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
|
||||
final Integer periodsRemaining = this.numberOfRepayments - periodsElapsed;
|
||||
|
||||
return FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(), principalDouble, futureValue,
|
||||
false);
|
||||
}
|
||||
|
||||
private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final MathContext mc, final int daysInPeriod, final Money outstandingBalance) {
|
||||
|
||||
Money interestDue = Money.zero(outstandingBalance.getCurrency());
|
||||
|
||||
switch (this.interestCalculationPeriodMethod) {
|
||||
case DAILY:
|
||||
BigDecimal dailyInterestRate = this.annualNominalInterestRate.divide(BigDecimal.valueOf(Long.valueOf(365)), mc)
|
||||
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc).multiply(BigDecimal.valueOf(this.repaymentEvery));
|
||||
|
||||
BigDecimal equivalentInterestRateForPeriod = dailyInterestRate.multiply(BigDecimal.valueOf(Long.valueOf(daysInPeriod)));
|
||||
|
||||
interestDue = outstandingBalance.multiplyRetainScale(equivalentInterestRateForPeriod, mc.getRoundingMode());
|
||||
break;
|
||||
case SAME_AS_REPAYMENT_PERIOD:
|
||||
final BigDecimal periodicInterestRate = periodicInterestRate(calculator, mc);
|
||||
interestDue = outstandingBalance.multiplyRetainScale(periodicInterestRate, mc.getRoundingMode());
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return interestDue;
|
||||
}
|
||||
|
||||
private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator,
|
||||
final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final int daysInPeriod,
|
||||
final Money outstandingBalance, final int periodNumber) {
|
||||
|
||||
Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, daysInPeriod, outstandingBalance);
|
||||
|
||||
if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) {
|
||||
interest = interest.zero();
|
||||
}
|
||||
|
||||
Double fraction = interestCalculationGraceOnRepaymentPeriodFraction;
|
||||
|
||||
if (isInterestFreeGracePeriod(periodNumber)) {
|
||||
interest = interest.zero();
|
||||
} else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) {
|
||||
|
||||
if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) {
|
||||
interest = interest.zero();
|
||||
fraction = fraction - Integer.valueOf(1).doubleValue();
|
||||
|
||||
} else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25")
|
||||
&& interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) {
|
||||
|
||||
Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction);
|
||||
interest = interest.minus(graceOnInterestForRepaymentPeriod);
|
||||
fraction = Double.valueOf("0");
|
||||
}
|
||||
}
|
||||
|
||||
return interest;
|
||||
}
|
||||
|
||||
private boolean isInterestFreeGracePeriodFromDate(final double interestCalculationGraceOnRepaymentPeriodFraction) {
|
||||
return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.0");
|
||||
}
|
||||
|
||||
private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) {
|
||||
|
||||
final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.numberOfRepayments);
|
||||
|
||||
Money principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode());
|
||||
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
|
||||
principal = principal.zero();
|
||||
}
|
||||
return principal;
|
||||
}
|
||||
|
||||
private Money calculatePrincipalDueForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc,
|
||||
final int periodNumber, final Money totalDuePerInstallment, final int daysInPeriod, final Money outstandingBalance) {
|
||||
|
||||
Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc,
|
||||
daysInPeriod, outstandingBalance);
|
||||
|
||||
Money principal = totalDuePerInstallment.minus(interestForThisInstallmentBeforeGrace);
|
||||
if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) {
|
||||
principal = principal.zero();
|
||||
}
|
||||
return principal;
|
||||
}
|
||||
|
||||
private Money calculateTotalDueForEqualInstallmentRepaymentPeriod(final BigDecimal periodicInterestRate, final Money balance,
|
||||
final int periodsElapsed) {
|
||||
|
||||
final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, balance, periodsElapsed);
|
||||
|
||||
return Money.of(balance.getCurrency(), BigDecimal.valueOf(paymentPerRepaymentPeriod));
|
||||
}
|
||||
|
||||
public LoanProductRelatedDetail toLoanProductRelatedDetail() {
|
||||
final MonetaryCurrency currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces());
|
||||
|
||||
return LoanProductRelatedDetail.createFrom(currency, this.principal.getAmount(), this.interestRatePerPeriod,
|
||||
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());
|
||||
}
|
||||
|
||||
public Integer getLoanTermFrequency() {
|
||||
return this.loanTermFrequency;
|
||||
}
|
||||
|
||||
public PeriodFrequencyType getLoanTermPeriodFrequencyType() {
|
||||
return this.loanTermPeriodFrequencyType;
|
||||
}
|
||||
|
||||
public Integer getRepaymentEvery() {
|
||||
return this.repaymentEvery;
|
||||
}
|
||||
|
||||
public PeriodFrequencyType getRepaymentPeriodFrequencyType() {
|
||||
return this.repaymentPeriodFrequencyType;
|
||||
}
|
||||
|
||||
public Date getRepaymentStartFromDate() {
|
||||
Date dateValue = null;
|
||||
if (this.repaymentsStartingFromDate != null) {
|
||||
dateValue = this.repaymentsStartingFromDate.toDate();
|
||||
}
|
||||
return dateValue;
|
||||
}
|
||||
|
||||
public Date getInterestChargedFromDate() {
|
||||
Date dateValue = null;
|
||||
if (this.interestChargedFromDate != null) {
|
||||
dateValue = this.interestChargedFromDate.toDate();
|
||||
}
|
||||
return dateValue;
|
||||
}
|
||||
|
||||
public LocalDate getInterestChargedFromLocalDate() {
|
||||
return this.interestChargedFromDate;
|
||||
}
|
||||
|
||||
public InterestMethod getInterestMethod() {
|
||||
return this.interestMethod;
|
||||
}
|
||||
|
||||
public AmortizationMethod getAmortizationMethod() {
|
||||
return this.amortizationMethod;
|
||||
}
|
||||
|
||||
public MonetaryCurrency getCurrency() {
|
||||
return this.principal.getCurrency();
|
||||
}
|
||||
|
||||
public Integer getNumberOfRepayments() {
|
||||
return this.numberOfRepayments;
|
||||
}
|
||||
|
||||
public LocalDate getExpectedDisbursementDate() {
|
||||
return this.expectedDisbursementDate;
|
||||
}
|
||||
|
||||
public LocalDate getRepaymentsStartingFromLocalDate() {
|
||||
return this.repaymentsStartingFromDate;
|
||||
}
|
||||
|
||||
public Money getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
}
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
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.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
/**
|
||||
* Domain representation of a Loan Schedule (not used for persistence)
|
||||
*/
|
||||
public final class LoanSchedule {
|
||||
|
||||
private final LoanScheduleGenerator loanScheduleGenerator;
|
||||
private final ApplicationCurrency applicationCurrency;
|
||||
private final BigDecimal principal;
|
||||
private final BigDecimal nominalInterestRatePerPeriod;
|
||||
private final PeriodFrequencyType interestRatePeriodFrequencyType;
|
||||
private final BigDecimal nominalAnnualInterestRate;
|
||||
private final InterestMethod interestMethod;
|
||||
private final InterestCalculationPeriodMethod interestCalculationPeriodMethod;
|
||||
private final Integer repaymentEvery;
|
||||
private final PeriodFrequencyType repaymentPeriodFrequencyType;
|
||||
private final Integer numberOfRepayments;
|
||||
private final AmortizationMethod amortizationMethod;
|
||||
private final Integer loanTermFrequency;
|
||||
private final PeriodFrequencyType loanTermPeriodFrequencyType;
|
||||
private final LocalDate disbursementDate;
|
||||
private final LocalDate repaymentStartFromDate;
|
||||
private final LocalDate interestChargedFromDate;
|
||||
private final BigDecimal inArrearsTolerance;
|
||||
|
||||
private final Set<LoanCharge> loanCharges;
|
||||
|
||||
public LoanSchedule(final LoanScheduleGenerator loanScheduleGenerator, final ApplicationCurrency applicationCurrency,
|
||||
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 AmortizationMethod amortizationMethod, final Integer loanTermFrequency,
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType, final Set<LoanCharge> loanCharges, final LocalDate disbursementDate,
|
||||
final LocalDate repaymentStartFromDate, final LocalDate interestChargedFromDate, final BigDecimal inArrearsTolerance) {
|
||||
this.loanScheduleGenerator = loanScheduleGenerator;
|
||||
this.applicationCurrency = applicationCurrency;
|
||||
this.principal = principal;
|
||||
this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod;
|
||||
this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType;
|
||||
this.nominalAnnualInterestRate = nominalAnnualInterestRate;
|
||||
this.interestMethod = interestMethod;
|
||||
this.interestCalculationPeriodMethod = interestCalculationPeriodMethod;
|
||||
this.repaymentEvery = repaymentEvery;
|
||||
this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType;
|
||||
this.numberOfRepayments = numberOfRepayments;
|
||||
this.amortizationMethod = amortizationMethod;
|
||||
this.loanTermFrequency = loanTermFrequency;
|
||||
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
|
||||
this.loanCharges = loanCharges;
|
||||
this.disbursementDate = disbursementDate;
|
||||
this.repaymentStartFromDate = repaymentStartFromDate;
|
||||
this.interestChargedFromDate = interestChargedFromDate;
|
||||
this.inArrearsTolerance = inArrearsTolerance;
|
||||
}
|
||||
|
||||
public LoanScheduleData generate() {
|
||||
return loanScheduleGenerator.generate(applicationCurrency, loanProductRelatedDetail(), loanTermFrequency,
|
||||
loanTermPeriodFrequencyType, disbursementDate, repaymentStartFromDate, interestChargedFromDate, loanCharges);
|
||||
}
|
||||
|
||||
public LoanProductRelatedDetail loanProductRelatedDetail() {
|
||||
final MonetaryCurrency currency = new MonetaryCurrency(applicationCurrency.getCode(), applicationCurrency.getDecimalPlaces());
|
||||
|
||||
return LoanProductRelatedDetail.createFrom(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
|
||||
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType,
|
||||
numberOfRepayments, amortizationMethod, this.inArrearsTolerance);
|
||||
}
|
||||
|
||||
public Integer getLoanTermFrequency() {
|
||||
return this.loanTermFrequency;
|
||||
}
|
||||
|
||||
public PeriodFrequencyType getLoanTermPeriodFrequencyType() {
|
||||
return this.loanTermPeriodFrequencyType;
|
||||
}
|
||||
|
||||
public LocalDate getDisbursementDate() {
|
||||
return this.disbursementDate;
|
||||
}
|
||||
|
||||
public LocalDate getRepaymentStartFromDate() {
|
||||
return this.repaymentStartFromDate;
|
||||
}
|
||||
|
||||
public LocalDate getInterestChargedFromDate() {
|
||||
return this.interestChargedFromDate;
|
||||
}
|
||||
}
|
||||
+3
-8
@@ -5,19 +5,14 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.MathContext;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public interface LoanScheduleGenerator {
|
||||
|
||||
LoanScheduleData generate(ApplicationCurrency applicationCurrency, LoanProductRelatedDetail loanScheduleInfo,
|
||||
Integer loanTermFrequency, PeriodFrequencyType loanTermFrequencyType, LocalDate disbursementDate, LocalDate firstRepaymentDate,
|
||||
LocalDate interestCalculatedFrom, Set<LoanCharge> loanCharges);
|
||||
|
||||
LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms,
|
||||
Set<LoanCharge> loanCharges);
|
||||
}
|
||||
-1
@@ -10,5 +10,4 @@ import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
|
||||
public interface LoanScheduleGeneratorFactory {
|
||||
|
||||
LoanScheduleGenerator create(InterestMethod interestMethod);
|
||||
|
||||
}
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
|
||||
/**
|
||||
* Domain representation of a Loan Schedule (not used for persistence)
|
||||
*/
|
||||
public final class LoanScheduleModel {
|
||||
|
||||
private final Collection<LoanScheduleModelPeriod> periods;
|
||||
private final ApplicationCurrency applicationCurrency;
|
||||
private final int loanTermInDays;
|
||||
private final Money totalPrincipalDisbursed;
|
||||
private final BigDecimal totalPrincipalExpected;
|
||||
private final BigDecimal totalPrincipalPaid;
|
||||
private final BigDecimal totalInterestCharged;
|
||||
private final BigDecimal totalFeeChargesCharged;
|
||||
private final BigDecimal totalPenaltyChargesCharged;
|
||||
private final BigDecimal totalRepaymentExpected;
|
||||
private final BigDecimal totalOutstanding;
|
||||
|
||||
public static LoanScheduleModel from(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
|
||||
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
|
||||
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
|
||||
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
|
||||
|
||||
return new LoanScheduleModel(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
|
||||
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
|
||||
totalOutstanding);
|
||||
}
|
||||
|
||||
private LoanScheduleModel(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
|
||||
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
|
||||
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
|
||||
final BigDecimal totalPenaltyChargesCharged, final BigDecimal totalRepaymentExpected, final BigDecimal totalOutstanding) {
|
||||
this.periods = periods;
|
||||
this.applicationCurrency = applicationCurrency;
|
||||
this.loanTermInDays = loanTermInDays;
|
||||
this.totalPrincipalDisbursed = principalDisbursed;
|
||||
this.totalPrincipalExpected = totalPrincipalExpected;
|
||||
this.totalPrincipalPaid = totalPrincipalPaid;
|
||||
this.totalInterestCharged = totalInterestCharged;
|
||||
this.totalFeeChargesCharged = totalFeeChargesCharged;
|
||||
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
|
||||
this.totalRepaymentExpected = totalRepaymentExpected;
|
||||
this.totalOutstanding = totalOutstanding;
|
||||
}
|
||||
|
||||
public LoanScheduleData toData() {
|
||||
|
||||
final int decimalPlaces = this.totalPrincipalDisbursed.getCurrencyDigitsAfterDecimal();
|
||||
final CurrencyData currency = applicationCurrency.toData(decimalPlaces);
|
||||
|
||||
final Collection<LoanSchedulePeriodData> periodsData = new ArrayList<LoanSchedulePeriodData>();
|
||||
for (LoanScheduleModelPeriod modelPeriod : this.periods) {
|
||||
periodsData.add(modelPeriod.toData());
|
||||
}
|
||||
|
||||
BigDecimal totalWaived = null;
|
||||
BigDecimal totalWrittenOff = null;
|
||||
BigDecimal totalRepayment = null;
|
||||
|
||||
return new LoanScheduleData(currency, periodsData, this.loanTermInDays, this.totalPrincipalDisbursed.getAmount(),
|
||||
this.totalPrincipalExpected, this.totalPrincipalPaid, this.totalInterestCharged, this.totalFeeChargesCharged,
|
||||
this.totalPenaltyChargesCharged, totalWaived, totalWrittenOff, this.totalRepaymentExpected, totalRepayment,
|
||||
this.totalOutstanding);
|
||||
}
|
||||
|
||||
public Collection<LoanScheduleModelPeriod> getPeriods() {
|
||||
return this.periods;
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
|
||||
/**
|
||||
* Domain representation of a Loan Schedule Disbursement Period (not used for
|
||||
* persistence)
|
||||
*/
|
||||
public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private final int periodNumber;
|
||||
private final LocalDate disbursementDate;
|
||||
private final Money principalDisbursed;
|
||||
private final BigDecimal chargesDueAtTimeOfDisbursement;
|
||||
|
||||
public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
|
||||
final int periodNumber = 0;
|
||||
return new LoanScheduleModelDisbursementPeriod(periodNumber, loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
||||
}
|
||||
|
||||
private LoanScheduleModelDisbursementPeriod(final int periodNumber, final LoanApplicationTerms loanApplicationTerms,
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.disbursementDate = loanApplicationTerms.getExpectedDisbursementDate();
|
||||
this.principalDisbursed = loanApplicationTerms.getPrincipal();
|
||||
this.chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedulePeriodData toData() {
|
||||
return LoanSchedulePeriodData.disbursementOnlyPeriod(this.disbursementDate, this.principalDisbursed.getAmount(),
|
||||
this.chargesDueAtTimeOfDisbursement, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepaymentPeriod() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer periodNumber() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodFromDate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodDueDate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal principalDue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal interestDue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal feeChargesDue() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal penaltyChargesDue() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
|
||||
public interface LoanScheduleModelPeriod {
|
||||
|
||||
LoanSchedulePeriodData toData();
|
||||
|
||||
boolean isRepaymentPeriod();
|
||||
|
||||
Integer periodNumber();
|
||||
|
||||
LocalDate periodFromDate();
|
||||
|
||||
LocalDate periodDueDate();
|
||||
|
||||
BigDecimal principalDue();
|
||||
|
||||
BigDecimal interestDue();
|
||||
|
||||
BigDecimal feeChargesDue();
|
||||
|
||||
BigDecimal penaltyChargesDue();
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
|
||||
/**
|
||||
* Domain representation of a Loan Schedule Repayment Period (not used for
|
||||
* persistence)
|
||||
*/
|
||||
public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
private final int periodNumber;
|
||||
private final LocalDate fromDate;
|
||||
private final LocalDate dueDate;
|
||||
private final Money principalDue;
|
||||
private final Money outstandingLoanBalance;
|
||||
private final Money interestDue;
|
||||
private final Money feeChargesDue;
|
||||
private final Money penaltyChargesDue;
|
||||
private final Money totalDue;
|
||||
|
||||
public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
|
||||
final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
|
||||
final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue) {
|
||||
|
||||
return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance,
|
||||
interestDue, feeChargesDue, penaltyChargesDue, totalDue);
|
||||
}
|
||||
|
||||
public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate,
|
||||
final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue,
|
||||
final Money penaltyChargesDue, final Money totalDue) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.principalDue = principalDue;
|
||||
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||
this.interestDue = interestDue;
|
||||
this.feeChargesDue = feeChargesDue;
|
||||
this.penaltyChargesDue = penaltyChargesDue;
|
||||
this.totalDue = totalDue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedulePeriodData toData() {
|
||||
return LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, this.fromDate, this.dueDate, this.principalDue.getAmount(),
|
||||
this.outstandingLoanBalance.getAmount(), this.interestDue.getAmount(), this.feeChargesDue.getAmount(),
|
||||
this.penaltyChargesDue.getAmount(), this.totalDue.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRepaymentPeriod() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer periodNumber() {
|
||||
return this.periodNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodFromDate() {
|
||||
return this.fromDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate periodDueDate() {
|
||||
return this.dueDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal principalDue() {
|
||||
BigDecimal value = null;
|
||||
if (this.principalDue != null) {
|
||||
value = this.principalDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal interestDue() {
|
||||
BigDecimal value = null;
|
||||
if (this.interestDue != null) {
|
||||
value = this.interestDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal feeChargesDue() {
|
||||
BigDecimal value = null;
|
||||
if (this.feeChargesDue != null) {
|
||||
value = this.feeChargesDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal penaltyChargesDue() {
|
||||
BigDecimal value = null;
|
||||
if (this.penaltyChargesDue != null) {
|
||||
value = this.penaltyChargesDue.getAmount();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
+3
-7
@@ -5,8 +5,6 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
@@ -14,9 +12,7 @@ public interface PaymentPeriodsInOneYearCalculator {
|
||||
|
||||
Integer calculate(PeriodFrequencyType repaymentFrequencyType);
|
||||
|
||||
double calculateRepaymentPeriodAsAFractionOfDays(
|
||||
PeriodFrequencyType repaymentPeriodFrequencyType,
|
||||
Integer every, LocalDate interestCalculatedFrom,
|
||||
List<LocalDate> scheduledDates, LocalDate disbursementDate);
|
||||
double calculatePortionOfRepaymentPeriodInterestChargingGrace(LocalDate repaymentPeriodStartDate, LocalDate scheduledDueDate,
|
||||
LocalDate interestChargedFromLocalDate, PeriodFrequencyType repaymentPeriodFrequencyType, Integer repaidEvery);
|
||||
|
||||
}
|
||||
}
|
||||
+65
-66
@@ -13,75 +13,74 @@ import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
@Deprecated
|
||||
public class PeriodicInterestRateCalculator {
|
||||
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
public BigDecimal calculateFrom(final PeriodFrequencyType loanTermFrequencyType, final BigDecimal annualNominalInterestRate) {
|
||||
|
||||
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
|
||||
|
||||
Integer paymentPeriodsInOneYear = this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType);
|
||||
|
||||
BigDecimal divisor = BigDecimal.valueOf(paymentPeriodsInOneYear * 100);
|
||||
return annualNominalInterestRate.divide(divisor, mc);
|
||||
}
|
||||
|
||||
public BigDecimal calculateFrom(final LoanProductRelatedDetail loanScheduleInfo) {
|
||||
|
||||
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
|
||||
|
||||
Integer paymentPeriodsInOneYear = this.paymentPeriodsInOneYearCalculator.calculate(loanScheduleInfo.getRepaymentPeriodFrequencyType());
|
||||
|
||||
BigDecimal divisor = BigDecimal.valueOf(paymentPeriodsInOneYear * 100);
|
||||
BigDecimal numberOfPeriods = BigDecimal.valueOf(loanScheduleInfo.getRepayEvery());
|
||||
|
||||
return loanScheduleInfo.getAnnualNominalInterestRate().divide(divisor, mc).multiply(numberOfPeriods);
|
||||
}
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
public Money calculateInterestOn(
|
||||
final Money outstandingBalance,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
final int daysInPeriod,
|
||||
final LoanProductRelatedDetail loanScheduleInfo) {
|
||||
public BigDecimal calculateFrom(final PeriodFrequencyType loanTermFrequencyType, final BigDecimal annualNominalInterestRate) {
|
||||
|
||||
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
|
||||
Money interestDue = Money.zero(outstandingBalance.getCurrency());
|
||||
|
||||
switch (loanScheduleInfo.getInterestCalculationPeriodMethod()) {
|
||||
case DAILY:
|
||||
BigDecimal dailyInterestRate = loanScheduleInfo.getAnnualNominalInterestRate()
|
||||
.divide(BigDecimal.valueOf(Long.valueOf(365)), mc)
|
||||
.divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc)
|
||||
.multiply(BigDecimal.valueOf(loanScheduleInfo.getRepayEvery()));
|
||||
|
||||
BigDecimal equivalentInterestRateForPeriod = dailyInterestRate.multiply(BigDecimal.valueOf(Long.valueOf(daysInPeriod)));
|
||||
|
||||
interestDue = outstandingBalance.multiplyRetainScale(equivalentInterestRateForPeriod, RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
default:
|
||||
interestDue = outstandingBalance.multiplyRetainScale(periodInterestRateForRepaymentPeriod, RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
}
|
||||
|
||||
return interestDue;
|
||||
}
|
||||
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
|
||||
|
||||
public Money calculatePrincipalOn(Money totalDuePerInstallment, Money interestForInstallment, LoanProductRelatedDetail loanScheduleInfo) {
|
||||
|
||||
Money principalDue = Money.zero(totalDuePerInstallment.getCurrency());
|
||||
|
||||
switch (loanScheduleInfo.getAmortizationMethod()) {
|
||||
case EQUAL_PRINCIPAL:
|
||||
principalDue = loanScheduleInfo.getPrincipal().dividedBy(loanScheduleInfo.getNumberOfRepayments(), RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
case EQUAL_INSTALLMENTS:
|
||||
principalDue = totalDuePerInstallment.minus(interestForInstallment);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return principalDue;
|
||||
}
|
||||
Integer paymentPeriodsInOneYear = this.paymentPeriodsInOneYearCalculator.calculate(loanTermFrequencyType);
|
||||
|
||||
BigDecimal divisor = BigDecimal.valueOf(paymentPeriodsInOneYear * 100);
|
||||
return annualNominalInterestRate.divide(divisor, mc);
|
||||
}
|
||||
|
||||
public BigDecimal calculateFrom(final LoanProductRelatedDetail loanScheduleInfo) {
|
||||
|
||||
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
|
||||
|
||||
Integer paymentPeriodsInOneYear = this.paymentPeriodsInOneYearCalculator.calculate(loanScheduleInfo
|
||||
.getRepaymentPeriodFrequencyType());
|
||||
|
||||
BigDecimal divisor = BigDecimal.valueOf(paymentPeriodsInOneYear * 100);
|
||||
BigDecimal numberOfPeriods = BigDecimal.valueOf(loanScheduleInfo.getRepayEvery());
|
||||
|
||||
return loanScheduleInfo.getAnnualNominalInterestRate().divide(divisor, mc).multiply(numberOfPeriods);
|
||||
}
|
||||
|
||||
public Money calculateInterestOn(final Money outstandingBalance, final BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
final int daysInPeriod, final LoanProductRelatedDetail loanScheduleInfo) {
|
||||
|
||||
MathContext mc = new MathContext(8, RoundingMode.HALF_EVEN);
|
||||
Money interestDue = Money.zero(outstandingBalance.getCurrency());
|
||||
|
||||
switch (loanScheduleInfo.getInterestCalculationPeriodMethod()) {
|
||||
case DAILY:
|
||||
BigDecimal dailyInterestRate = loanScheduleInfo.getAnnualNominalInterestRate()
|
||||
.divide(BigDecimal.valueOf(Long.valueOf(365)), mc).divide(BigDecimal.valueOf(Double.valueOf("100.0")), mc)
|
||||
.multiply(BigDecimal.valueOf(loanScheduleInfo.getRepayEvery()));
|
||||
|
||||
BigDecimal equivalentInterestRateForPeriod = dailyInterestRate.multiply(BigDecimal.valueOf(Long.valueOf(daysInPeriod)));
|
||||
|
||||
interestDue = outstandingBalance.multiplyRetainScale(equivalentInterestRateForPeriod, RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
default:
|
||||
interestDue = outstandingBalance.multiplyRetainScale(periodInterestRateForRepaymentPeriod, RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
}
|
||||
|
||||
return interestDue;
|
||||
}
|
||||
|
||||
public Money calculatePrincipalOn(final Money totalDuePerInstallment, final Money interestForInstallment,
|
||||
final LoanProductRelatedDetail loanScheduleInfo) {
|
||||
|
||||
Money principalDue = Money.zero(totalDuePerInstallment.getCurrency());
|
||||
|
||||
switch (loanScheduleInfo.getAmortizationMethod()) {
|
||||
case EQUAL_PRINCIPAL:
|
||||
principalDue = loanScheduleInfo.getPrincipal().dividedBy(loanScheduleInfo.getNumberOfRepayments(), RoundingMode.HALF_EVEN);
|
||||
break;
|
||||
case EQUAL_INSTALLMENTS:
|
||||
principalDue = totalDuePerInstallment.minus(interestForInstallment);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
|
||||
return principalDue;
|
||||
}
|
||||
}
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
|
||||
public class PmtCalculator {
|
||||
|
||||
public Money calculatePaymentForOnePeriodFrom(final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
final MonetaryCurrency monetaryCurrency) {
|
||||
double interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
|
||||
|
||||
double futureValue = 0;
|
||||
double numberOfPeriods = loanScheduleInfo.getNumberOfRepayments().doubleValue();
|
||||
double principal = loanScheduleInfo.getPrincipal().getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
|
||||
// work out the total payment (principal + interest components) for each installment due.
|
||||
// use the period type and interest as expressed e.g. 2% per month or 24% per year even is 'daily' interest calculation is selected
|
||||
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principal, futureValue, false);
|
||||
|
||||
return Money.of(monetaryCurrency, BigDecimal.valueOf(paymentPerInstallment));
|
||||
}
|
||||
|
||||
public Money calculateTotalRepaymentFrom(final LoanProductRelatedDetail loanScheduleInfo,
|
||||
final BigDecimal periodInterestRateForRepaymentPeriod,
|
||||
final MonetaryCurrency monetaryCurrency) {
|
||||
double interestRateFraction = periodInterestRateForRepaymentPeriod.doubleValue();
|
||||
|
||||
double futureValue = 0;
|
||||
double numberOfPeriods = loanScheduleInfo.getNumberOfRepayments().doubleValue();
|
||||
double principal = loanScheduleInfo.getPrincipal().getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
|
||||
// work out the total payment (principal + interest components) for each installment due.
|
||||
// use the period type and interest as expressed e.g. 2% per month or 24% per year even is 'daily' interest calculation is selected
|
||||
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principal, futureValue, false);
|
||||
|
||||
double totalRepayment = paymentPerInstallment * loanScheduleInfo.getNumberOfRepayments();
|
||||
|
||||
return Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment));
|
||||
}
|
||||
|
||||
public Money calculateTotalRepaymentFrom(
|
||||
final Money principal,
|
||||
final Integer loanTermFrequency,
|
||||
final BigDecimal periodInterestRateForLoanTermPeriod,
|
||||
final MonetaryCurrency monetaryCurrency) {
|
||||
|
||||
double interestRateFraction = periodInterestRateForLoanTermPeriod.doubleValue();
|
||||
|
||||
double futureValue = 0;
|
||||
double numberOfPeriods = loanTermFrequency.doubleValue();
|
||||
double principalAmount = principal.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
|
||||
|
||||
// work out the total payment (principal + interest components) for each installment due.
|
||||
// use the period type and interest as expressed e.g. 2% per month or 24% per year even is 'daily' interest calculation is selected
|
||||
double paymentPerInstallment = pmt(interestRateFraction, numberOfPeriods, principalAmount, futureValue, false);
|
||||
|
||||
double totalRepayment = paymentPerInstallment * loanTermFrequency;
|
||||
|
||||
return Money.of(monetaryCurrency, BigDecimal.valueOf(totalRepayment));
|
||||
}
|
||||
|
||||
/**
|
||||
* PMT calculates a fixed monthly payment to be paid by borrower every 'period' to ensure loan is paid off in full (with interest).
|
||||
*
|
||||
* This monthly payment c depends upon
|
||||
* the monthly interest rate r (expressed as a fraction, not a percentage,
|
||||
* i.e., divide the quoted yearly percentage rate by 100 and by 12 to obtain
|
||||
* the monthly interest rate), the number of monthly payments N called the
|
||||
* loan's term, and the amount borrowed P known as the loan's principal; c
|
||||
* is given by the formula:
|
||||
*
|
||||
* c = (r / (1 - (1 + r)^-N))P
|
||||
*
|
||||
* @param interestRateFraction
|
||||
* @param numberOfPayments
|
||||
* @param principal
|
||||
* @param futureValue
|
||||
* @param type
|
||||
*/
|
||||
private static double pmt(double interestRateFraction, double numberOfPayments, double principal, double futureValue, boolean type) {
|
||||
double payment = 0;
|
||||
if (interestRateFraction == 0) {
|
||||
payment = -1 * (futureValue + principal) / numberOfPayments;
|
||||
} else {
|
||||
double r1 = interestRateFraction + 1;
|
||||
payment = (futureValue + principal * Math.pow(r1, numberOfPayments)) * interestRateFraction
|
||||
/ ((type ? r1 : 1) * (1 - Math.pow(r1, numberOfPayments)));
|
||||
}
|
||||
return payment;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
|
||||
public class PrincipalInterest {
|
||||
|
||||
private final Money principal;
|
||||
private final Money interest;
|
||||
private final Money interestPaymentDueToGrace;
|
||||
|
||||
public PrincipalInterest(final Money principal, final Money interest, final Money interestPaymentDueToGrace) {
|
||||
this.principal = principal;
|
||||
this.interest = interest;
|
||||
this.interestPaymentDueToGrace = interestPaymentDueToGrace;
|
||||
}
|
||||
|
||||
public Money principal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
public Money interest() {
|
||||
return this.interest;
|
||||
}
|
||||
|
||||
public Money interestPaymentDueToGrace() {
|
||||
return this.interestPaymentDueToGrace;
|
||||
}
|
||||
}
|
||||
+4
-9
@@ -8,17 +8,12 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
|
||||
import java.util.List;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public interface ScheduledDateGenerator {
|
||||
|
||||
List<LocalDate> generate(
|
||||
LoanProductMinimumRepaymentScheduleRelatedDetail loanScheduleDateInfo,
|
||||
LocalDate disbursementDate, LocalDate firstRepaymentDate);
|
||||
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(
|
||||
LoanProductRelatedDetail loanScheduleInfo,
|
||||
List<LocalDate> scheduledDates);
|
||||
List<LocalDate> generate(LoanApplicationTerms loanApplicationTerms);
|
||||
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
|
||||
List<LocalDate> scheduledDates);
|
||||
}
|
||||
|
||||
+80
-44
@@ -6,6 +6,8 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
@@ -13,16 +15,19 @@ import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
import org.mifosplatform.portfolio.loanaccount.service.LoanChargeAssembler;
|
||||
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.LoanProduct;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
|
||||
@@ -54,65 +59,96 @@ public class LoanScheduleAssembler {
|
||||
this.loanChargeAssembler = loanChargeAssembler;
|
||||
}
|
||||
|
||||
public LoanSchedule fromJson(final String json) {
|
||||
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||
return fromJson(element);
|
||||
}
|
||||
|
||||
public LoanSchedule fromJson(final JsonElement element) {
|
||||
return fromJson(element, null);
|
||||
}
|
||||
|
||||
public LoanSchedule fromJson(final JsonElement element, final BigDecimal inArrearsTolerance) {
|
||||
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
|
||||
final Long loanProductId = fromApiJsonHelper.extractLongNamed("productId", element);
|
||||
|
||||
final LoanProduct loanProduct = this.loanProductRepository.findOne(loanProductId);
|
||||
if (loanProduct == null) { throw new LoanProductNotFoundException(loanProductId); }
|
||||
|
||||
return assembleLoanApplicationTermsFrom(element, loanProduct);
|
||||
}
|
||||
|
||||
private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement element, final LoanProduct loanProduct) {
|
||||
|
||||
final MonetaryCurrency currency = loanProduct.getCurrency();
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
|
||||
final BigDecimal principal = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
|
||||
final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
|
||||
//final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestRateFrequencyType", element);
|
||||
final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
|
||||
final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType",
|
||||
element);
|
||||
|
||||
final InterestMethod interestMethod = InterestMethod.fromInt(interestType);
|
||||
//final PeriodFrequencyType interestRatePeriodFrequencyType = PeriodFrequencyType.fromInt(interestRateFrequencyType);
|
||||
|
||||
// PeriodFrequencyType is copied from Loan Product
|
||||
final PeriodFrequencyType interestRatePeriodFrequencyType = loanProduct.loanProductRelatedDetail().getInterestPeriodFrequencyType();
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
|
||||
.fromInt(interestCalculationPeriodType);
|
||||
|
||||
final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType,
|
||||
interestRatePerPeriod);
|
||||
|
||||
final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element);
|
||||
final Integer repaymentFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element);
|
||||
final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
|
||||
final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element);
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType);
|
||||
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType);
|
||||
|
||||
// loan terms
|
||||
final Integer loanTermFrequency = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequency", element);
|
||||
final Integer loanTermFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequencyType", element);
|
||||
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(loanTermFrequencyType);
|
||||
|
||||
final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
|
||||
final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element);
|
||||
final Integer numberOfRepayments = fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
|
||||
final Integer repaymentEvery = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element);
|
||||
final Integer repaymentFrequencyType = fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element);
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType);
|
||||
|
||||
final Integer amortizationType = fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element);
|
||||
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType);
|
||||
|
||||
// interest terms
|
||||
final Integer interestType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
|
||||
final InterestMethod interestMethod = InterestMethod.fromInt(interestType);
|
||||
|
||||
final Integer interestCalculationPeriodType = fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType",
|
||||
element);
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
|
||||
.fromInt(interestCalculationPeriodType);
|
||||
|
||||
final BigDecimal interestRatePerPeriod = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
|
||||
final PeriodFrequencyType interestRatePeriodFrequencyType = loanProduct.getInterestPeriodFrequencyType();
|
||||
|
||||
final BigDecimal annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType,
|
||||
interestRatePerPeriod);
|
||||
|
||||
// disbursement details
|
||||
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
|
||||
final Money principalMoney = Money.of(currency, principal);
|
||||
|
||||
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
|
||||
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper.extractLocalDateNamed("repaymentsStartingFromDate", element);
|
||||
|
||||
// grace details
|
||||
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
||||
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
||||
final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
|
||||
final LocalDate interestChargedFromDate = fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element);
|
||||
|
||||
// other
|
||||
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
|
||||
final Money inArrearsToleranceMoney = Money.of(currency, inArrearsTolerance);
|
||||
|
||||
return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
|
||||
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
|
||||
expectedDisbursementDate, repaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney);
|
||||
}
|
||||
|
||||
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) {
|
||||
LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
|
||||
return loanApplicationTerms.toLoanProductRelatedDetail();
|
||||
}
|
||||
|
||||
public LoanScheduleModel assembleLoanScheduleFrom(final JsonElement element) {
|
||||
|
||||
final LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
|
||||
|
||||
return assembleLoanScheduleFrom(loanApplicationTerms, element);
|
||||
}
|
||||
|
||||
public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final JsonElement element) {
|
||||
|
||||
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element);
|
||||
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod);
|
||||
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
|
||||
|
||||
return new LoanSchedule(loanScheduleGenerator, applicationCurrency, principal, interestRatePerPeriod,
|
||||
interestRatePeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, amortizationMethod, loanTermFrequency,
|
||||
loanTermPeriodFrequencyType, loanCharges, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate,
|
||||
inArrearsTolerance);
|
||||
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
|
||||
final MathContext mc = new MathContext(8, roundingMode);
|
||||
|
||||
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
|
||||
return loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, loanCharges);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -6,9 +6,9 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.api.JsonQuery;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
|
||||
public interface LoanScheduleCalculationPlatformService {
|
||||
|
||||
LoanScheduleData calculateLoanSchedule(JsonQuery query);
|
||||
LoanScheduleModel calculateLoanSchedule(JsonQuery query);
|
||||
}
|
||||
|
||||
+4
-11
@@ -6,9 +6,7 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.loanschedule.service;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.api.JsonQuery;
|
||||
import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
import org.mifosplatform.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonHelper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -16,26 +14,21 @@ import org.springframework.stereotype.Service;
|
||||
@Service
|
||||
public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleCalculationPlatformService {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer;
|
||||
private final LoanScheduleAssembler loanScheduleAssembler;
|
||||
|
||||
@Autowired
|
||||
public LoanScheduleCalculationPlatformServiceImpl(final PlatformSecurityContext context,
|
||||
final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer,
|
||||
public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer,
|
||||
final LoanScheduleAssembler loanScheduleAssembler) {
|
||||
this.context = context;
|
||||
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
||||
this.loanScheduleAssembler = loanScheduleAssembler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanScheduleData calculateLoanSchedule(final JsonQuery query) {
|
||||
context.authenticatedUser();
|
||||
public LoanScheduleModel calculateLoanSchedule(final JsonQuery query) {
|
||||
|
||||
this.fromApiJsonDeserializer.validate(query.json());
|
||||
|
||||
final LoanSchedule loanSchedule = this.loanScheduleAssembler.fromJson(query.parsedJson());
|
||||
return loanSchedule.generate();
|
||||
return this.loanScheduleAssembler.assembleLoanScheduleFrom(query.parsedJson());
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -40,8 +40,9 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
|
||||
"productId", "accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId",
|
||||
"principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency",
|
||||
"loanTermFrequencyType", "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType",
|
||||
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "interestChargedFromDate",
|
||||
"submittedOnDate", "submittedOnNote", "locale", "dateFormat", "charges", "collateral"));
|
||||
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment",
|
||||
"graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale",
|
||||
"dateFormat", "charges", "collateral"));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
|
||||
+50
-23
@@ -40,8 +40,9 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
*/
|
||||
final Set<String> supportedParameters = new HashSet<String>(Arrays.asList("dateFormat", "locale", "id", "clientId", "groupId",
|
||||
"loanType", "productId", "principal", "loanTermFrequency", "loanTermFrequencyType", "numberOfRepayments", "repaymentEvery",
|
||||
"repaymentFrequencyType", "interestRatePerPeriod", "amortizationType", "interestType",
|
||||
"interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "interestChargedFromDate",
|
||||
"repaymentFrequencyType", "interestRatePerPeriod", "amortizationType", "interestType", "interestCalculationPeriodType",
|
||||
"expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment", "graceOnInterestPayment",
|
||||
"graceOnInterestCharged", "interestChargedFromDate", //
|
||||
"submittedOnDate", "submittedOnNote", //
|
||||
"accountNo", "externalId", "fundId", "loanOfficerId", // optional
|
||||
"loanPurposeId", "inArrearsTolerance", "charges", "collateral", // optional
|
||||
@@ -50,7 +51,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
|
||||
@Autowired
|
||||
public LoanApplicationCommandFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
@@ -70,14 +71,14 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
final String loanTypeParameterName = "loanType";
|
||||
final String loanTypeStr = fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
|
||||
baseDataValidator.reset().parameter(loanTypeParameterName).value(loanTypeStr).notNull();
|
||||
|
||||
if(!StringUtils.isBlank(loanTypeStr)){
|
||||
|
||||
if (!StringUtils.isBlank(loanTypeStr)) {
|
||||
final LoanType loanType = LoanType.fromName(loanTypeStr);
|
||||
baseDataValidator.reset().parameter(loanTypeParameterName).value(loanType.getValue()).inMinMaxRange(1, 3);
|
||||
|
||||
|
||||
final Long clientId = fromApiJsonHelper.extractLongNamed("clientId", element);
|
||||
final Long groupId = fromApiJsonHelper.extractLongNamed("groupId", element);
|
||||
if(loanType.isIndividualLoan()){
|
||||
if (loanType.isIndividualLoan()) {
|
||||
baseDataValidator.reset().parameter("clientId").value(clientId).notNull().longGreaterThanZero();
|
||||
baseDataValidator.reset().parameter("groupId").value(groupId).mustBeBlankWhenParameterProvided("clientId", clientId);
|
||||
}
|
||||
@@ -93,7 +94,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
final Long productId = fromApiJsonHelper.extractLongNamed("productId", element);
|
||||
baseDataValidator.reset().parameter("productId").value(productId).notNull().integerGreaterThanZero();
|
||||
|
||||
@@ -176,13 +177,15 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
final LocalDate expectedDisbursementDate = fromApiJsonHelper.extractLocalDateNamed(expectedDisbursementDateParameterName, element);
|
||||
baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDate).notNull();
|
||||
|
||||
final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate";
|
||||
if (fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) {
|
||||
final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed(repaymentsStartingFromDateParameterName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull()
|
||||
.notNull();
|
||||
}
|
||||
// grace validation
|
||||
final Integer graceOnPrincipalPayment = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
||||
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
|
||||
|
||||
final Integer graceOnInterestPayment = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
||||
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment).zeroOrPositiveAmount();
|
||||
|
||||
final Integer graceOnInterestCharged = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
|
||||
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged).zeroOrPositiveAmount();
|
||||
|
||||
final String interestChargedFromDateParameterName = "interestChargedFromDate";
|
||||
if (fromApiJsonHelper.parameterExists(interestChargedFromDateParameterName, element)) {
|
||||
@@ -192,6 +195,14 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
.notNull();
|
||||
}
|
||||
|
||||
final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate";
|
||||
if (fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) {
|
||||
final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed(repaymentsStartingFromDateParameterName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull()
|
||||
.notNull();
|
||||
}
|
||||
|
||||
final String inArrearsToleranceParameterName = "inArrearsTolerance";
|
||||
if (fromApiJsonHelper.parameterExists(inArrearsToleranceParameterName, element)) {
|
||||
final BigDecimal inArrearsTolerance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed(inArrearsToleranceParameterName,
|
||||
@@ -304,7 +315,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
|
||||
final JsonElement element = fromApiJsonHelper.parse(json);
|
||||
boolean atLeastOneParameterPassedForUpdate = false;
|
||||
|
||||
|
||||
final String clientIdParameterName = "clientId";
|
||||
if (fromApiJsonHelper.parameterExists(clientIdParameterName, element)) {
|
||||
atLeastOneParameterPassedForUpdate = true;
|
||||
@@ -459,12 +470,20 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
baseDataValidator.reset().parameter(expectedDisbursementDateParameterName).value(expectedDisbursementDate).notNull();
|
||||
}
|
||||
|
||||
final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate";
|
||||
if (fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) {
|
||||
atLeastOneParameterPassedForUpdate = true;
|
||||
final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed(repaymentsStartingFromDateParameterName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull();
|
||||
// grace validation
|
||||
if (fromApiJsonHelper.parameterExists("graceOnPrincipalPayment", element)) {
|
||||
final Integer graceOnPrincipalPayment = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
||||
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
if (fromApiJsonHelper.parameterExists("graceOnInterestPayment", element)) {
|
||||
final Integer graceOnInterestPayment = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
||||
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment).zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
if (fromApiJsonHelper.parameterExists("graceOnInterestCharged", element)) {
|
||||
final Integer graceOnInterestCharged = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
|
||||
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged).zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
final String interestChargedFromDateParameterName = "interestChargedFromDate";
|
||||
@@ -475,6 +494,14 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
baseDataValidator.reset().parameter(interestChargedFromDateParameterName).value(interestChargedFromDate).ignoreIfNull();
|
||||
}
|
||||
|
||||
final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate";
|
||||
if (fromApiJsonHelper.parameterExists(repaymentsStartingFromDateParameterName, element)) {
|
||||
atLeastOneParameterPassedForUpdate = true;
|
||||
final LocalDate repaymentsStartingFromDate = fromApiJsonHelper.extractLocalDateNamed(repaymentsStartingFromDateParameterName,
|
||||
element);
|
||||
baseDataValidator.reset().parameter(repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate).ignoreIfNull();
|
||||
}
|
||||
|
||||
final String submittedOnDateParameterName = "submittedOnDate";
|
||||
if (fromApiJsonHelper.parameterExists(submittedOnDateParameterName, element)) {
|
||||
atLeastOneParameterPassedForUpdate = true;
|
||||
@@ -593,7 +620,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist",
|
||||
"Validation errors exist.", dataValidationErrors); }
|
||||
}
|
||||
|
||||
|
||||
public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) {
|
||||
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
|
||||
+22
-18
@@ -51,8 +51,8 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateExce
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
|
||||
import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
|
||||
import org.mifosplatform.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator;
|
||||
@@ -60,7 +60,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
|
||||
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
|
||||
import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductCommandFromApiJsonDeserializer;
|
||||
import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductDataValidator;
|
||||
import org.mifosplatform.portfolio.note.domain.Note;
|
||||
import org.mifosplatform.portfolio.note.domain.NoteRepository;
|
||||
import org.mifosplatform.useradministration.domain.AppUser;
|
||||
@@ -81,7 +81,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
private final PlatformSecurityContext context;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
private final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator;
|
||||
private final LoanProductCommandFromApiJsonDeserializer loanProductCommandFromApiJsonDeserializer;
|
||||
private final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer;
|
||||
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
|
||||
private final LoanRepository loanRepository;
|
||||
private final NoteRepository noteRepository;
|
||||
@@ -102,7 +102,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper,
|
||||
final LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator,
|
||||
final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer,
|
||||
final LoanProductCommandFromApiJsonDeserializer loanProductCommandFromApiJsonDeserializer, final AprCalculator aprCalculator,
|
||||
final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer, final AprCalculator aprCalculator,
|
||||
final LoanAssembler loanAssembler, final LoanChargeAssembler loanChargeAssembler,
|
||||
final CollateralAssembler loanCollateralAssembler, final LoanRepository loanRepository, final NoteRepository noteRepository,
|
||||
final LoanScheduleCalculationPlatformService calculationPlatformService, final ClientRepositoryWrapper clientRepository,
|
||||
@@ -294,7 +294,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
final JsonElement parsedQuery = this.fromJsonHelper.parse(command.json());
|
||||
final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromJsonHelper);
|
||||
|
||||
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query);
|
||||
final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query);
|
||||
existingLoanApplication.updateLoanSchedule(loanSchedule);
|
||||
}
|
||||
|
||||
@@ -536,14 +536,16 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
final LocalDate edDate = command.localDateValueOfParameterNamed("expectedDisbursementDate");
|
||||
|
||||
if (edDate != null) {
|
||||
//Expected disbursement date should not be before Meeting start date
|
||||
if(edDate.isBefore(calendar.getStartDateLocalDate())){
|
||||
// Expected disbursement date should not be before Meeting start
|
||||
// date
|
||||
if (edDate.isBefore(calendar.getStartDateLocalDate())) {
|
||||
final String errorMessage = "Expected disbursement date '" + edDate.toString() + "' cannot be before meeting start date '"
|
||||
+ calendar.getStartDate().toString() + "' ";
|
||||
throw new LoanApplicationDateException("disbursement.date.cannot.be.before.meeting.start.date", errorMessage, edDate, calendar.getStartDate());
|
||||
throw new LoanApplicationDateException("disbursement.date.cannot.be.before.meeting.start.date", errorMessage, edDate,
|
||||
calendar.getStartDate());
|
||||
}
|
||||
|
||||
//Disbursement date should fall on a meeting date
|
||||
|
||||
// Disbursement date should fall on a meeting date
|
||||
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), edDate)) {
|
||||
final String errorMessage = "Expected disbursement date '" + edDate.toString() + "' does not fall on a meeting date.";
|
||||
throw new NotValidRecurringDateException("loan.expected.disbursement.date", errorMessage, edDate.toString(),
|
||||
@@ -553,18 +555,20 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
}
|
||||
|
||||
final LocalDate repStartingDate = command.localDateValueOfParameterNamed("repaymentsStartingFromDate");
|
||||
|
||||
|
||||
if (repStartingDate != null) {
|
||||
|
||||
//First repayment date should not be before Meeting date
|
||||
if(repStartingDate.isBefore(calendar.getStartDateLocalDate())){
|
||||
final String errorMessage = "First repayment date '" + repStartingDate.toString() + "' cannot be before meeting start date '"
|
||||
+ calendar.getStartDate().toString() + "' ";
|
||||
throw new LoanApplicationDateException("first.repayment.date.cannot.be.before.meeting.start.date", errorMessage, repStartingDate, calendar.getStartDate());
|
||||
// First repayment date should not be before Meeting date
|
||||
if (repStartingDate.isBefore(calendar.getStartDateLocalDate())) {
|
||||
final String errorMessage = "First repayment date '" + repStartingDate.toString()
|
||||
+ "' cannot be before meeting start date '" + calendar.getStartDate().toString() + "' ";
|
||||
throw new LoanApplicationDateException("first.repayment.date.cannot.be.before.meeting.start.date", errorMessage,
|
||||
repStartingDate, calendar.getStartDate());
|
||||
}
|
||||
|
||||
|
||||
if (!CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repStartingDate)) {
|
||||
final String errorMessage = "The First repayment date '" + repStartingDate.toString() + "' does not fall on a meeting date.";
|
||||
final String errorMessage = "The First repayment date '" + repStartingDate.toString()
|
||||
+ "' does not fall on a meeting date.";
|
||||
throw new NotValidRecurringDateException("loan.first.repayment.date", errorMessage, repStartingDate.toString(),
|
||||
calendar.getTitle());
|
||||
}
|
||||
|
||||
+16
-13
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
package org.mifosplatform.portfolio.loanaccount.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -40,9 +39,11 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanSchedule;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
|
||||
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.exception.InvalidCurrencyException;
|
||||
@@ -131,22 +132,21 @@ public class LoanAssembler {
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal inArrearsTolerance = fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
|
||||
final LoanSchedule loanSchedule = this.loanScheduleAssembler.fromJson(element, inArrearsTolerance);
|
||||
|
||||
Loan loanApplication = null;
|
||||
Client client = null;
|
||||
Group group = null;
|
||||
|
||||
|
||||
final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element);
|
||||
|
||||
final String loanTypeParameterName = "loanType";
|
||||
final String loanTypeStr = fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
|
||||
final EnumOptionData loanType = LoanEnumerations.loanType(loanTypeStr);
|
||||
|
||||
|
||||
if (clientId != null) {
|
||||
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
|
||||
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund,
|
||||
loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanSchedule, loanCharges, collateral);
|
||||
loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral);
|
||||
}
|
||||
|
||||
if (groupId != null) {
|
||||
@@ -154,15 +154,15 @@ public class LoanAssembler {
|
||||
if (group == null) { throw new GroupNotFoundException(groupId); }
|
||||
|
||||
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
|
||||
loanTransactionProcessingStrategy, loanSchedule, loanCharges);
|
||||
loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges);
|
||||
}
|
||||
|
||||
if (client != null && group != null) {
|
||||
|
||||
if (!group.hasClientAsMember(client)) { throw new ClientNotInGroupException(clientId, groupId); }
|
||||
|
||||
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
|
||||
loanTransactionProcessingStrategy, loanSchedule, loanCharges);
|
||||
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(),
|
||||
loanProduct, fund, loanOfficer, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges);
|
||||
}
|
||||
|
||||
final String externalId = fromApiJsonHelper.extractStringNamed("externalId", element);
|
||||
@@ -172,8 +172,11 @@ public class LoanAssembler {
|
||||
loanApplication.setHelpers(defaultLoanLifecycleStateMachine(), this.loanSummaryWrapper,
|
||||
this.loanRepaymentScheduleTransactionProcessorFactory);
|
||||
|
||||
loanApplication
|
||||
.loanApplicationSubmittal(currentUser, loanSchedule, defaultLoanLifecycleStateMachine(), submittedOnDate, externalId);
|
||||
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
|
||||
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, element);
|
||||
|
||||
loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(),
|
||||
submittedOnDate, externalId);
|
||||
|
||||
return loanApplication;
|
||||
}
|
||||
|
||||
+7
-3
@@ -95,7 +95,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
private final PaginationHelper<LoanAccountData> paginationHelper = new PaginationHelper<LoanAccountData>();
|
||||
private final LoanMapper loaanLoanMapper = new LoanMapper();
|
||||
|
||||
|
||||
@Autowired
|
||||
public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository,
|
||||
final LoanTransactionRepository loanTransactionRepository,
|
||||
@@ -402,6 +401,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
+ " l.closedon_date as closedOnDate, cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,"
|
||||
+ " l.expected_firstrepaymenton_date as expectedFirstRepaymentOnDate, l.interest_calculated_from_date as interestChargedFromDate, l.expected_maturedon_date as expectedMaturityDate, "
|
||||
+ " l.principal_amount as principal, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery,"
|
||||
+ " l.grace_on_principal_periods as graceOnPrincipalPayment, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,"
|
||||
+ " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, "
|
||||
+ " l.repayment_period_frequency_enum as repaymentFrequencyType, l.interest_period_frequency_enum as interestRateFrequencyType, "
|
||||
+ " l.term_frequency as termFrequency, l.term_period_frequency_enum as termPeriodFrequencyType, "
|
||||
@@ -550,6 +550,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod");
|
||||
final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate");
|
||||
|
||||
final Integer graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment");
|
||||
final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment");
|
||||
final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged");
|
||||
|
||||
final Integer termFrequency = JdbcSupport.getInteger(rs, "termFrequency");
|
||||
final Integer termPeriodFrequencyTypeInt = JdbcSupport.getInteger(rs, "termPeriodFrequencyType");
|
||||
final EnumOptionData termPeriodFrequencyType = LoanEnumerations.termFrequencyType(termPeriodFrequencyTypeInt);
|
||||
@@ -645,7 +649,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
loanOfficerId, loanOfficerName, currencyData, principal, inArrearsTolerance, termFrequency, termPeriodFrequencyType,
|
||||
numberOfRepayments, repaymentEvery, repaymentFrequencyType, transactionStrategyId, amortizationType,
|
||||
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType,
|
||||
expectedFirstRepaymentOnDate, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged);
|
||||
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
|
||||
interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,7 +869,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
@Override
|
||||
public LoanAccountData retrieveLoanProductDetailsTemplate(final Long productId) {
|
||||
|
||||
|
||||
context.authenticatedUser();
|
||||
|
||||
final LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId);
|
||||
|
||||
+12
-11
@@ -62,6 +62,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFoundException;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||
import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
|
||||
import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
|
||||
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
|
||||
@@ -91,6 +92,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final LoanSummaryWrapper loanSummaryWrapper;
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
|
||||
|
||||
@@ -103,7 +105,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LoanChargeRepository loanChargeRepository, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
|
||||
final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanSummaryWrapper loanSummaryWrapper,
|
||||
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
|
||||
final CalendarInstanceRepository calendarInstanceRepository,
|
||||
final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository,
|
||||
final PaymentDetailWritePlatformService paymentDetailWritePlatformService) {
|
||||
this.context = context;
|
||||
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
|
||||
@@ -118,6 +120,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.loanUpdateCommandFromApiJsonDeserializer = loanUpdateCommandFromApiJsonDeserializer;
|
||||
this.loanSummaryWrapper = loanSummaryWrapper;
|
||||
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
|
||||
this.loanScheduleFactory = loanScheduleFactory;
|
||||
this.calendarInstanceRepository = calendarInstanceRepository;
|
||||
this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
|
||||
}
|
||||
@@ -152,8 +155,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
|
||||
loan.disburse(currentUser, command, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, changes,
|
||||
paymentDetail);
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, paymentDetail);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.loanRepository.save(loan);
|
||||
@@ -178,7 +181,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
.build();
|
||||
}
|
||||
|
||||
private void validateDisbursementDate(LocalDate disbursementDate, Collection<CalendarInstance> calendarInstances) {
|
||||
private void validateDisbursementDate(final LocalDate disbursementDate, final Collection<CalendarInstance> calendarInstances) {
|
||||
if (!CollectionUtils.isEmpty(calendarInstances)) {
|
||||
Calendar calendar = null;
|
||||
for (CalendarInstance calendarInstance : calendarInstances) {
|
||||
@@ -208,16 +211,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
}
|
||||
}
|
||||
|
||||
/****
|
||||
* TODO Vishwas: Pair with Ashok and re-factor collection sheet code-base
|
||||
*****/
|
||||
@Transactional
|
||||
@Override
|
||||
public Map<String, Object> bulkLoanDisbursal(final JsonCommand command, final CollectionSheetBulkDisbursalCommand bulkDisbursalCommand) {
|
||||
final AppUser currentUser = context.authenticatedUser();
|
||||
|
||||
/****
|
||||
* TODO Vishwas: Pair with Ashok and re-factor collection sheet
|
||||
* code-base
|
||||
*****/
|
||||
|
||||
SingleDisbursalCommand[] disbursalCommand = bulkDisbursalCommand.getDisburseTransactions();
|
||||
Map<String, Object> changes = new LinkedHashMap<String, Object>();
|
||||
if (disbursalCommand == null) { return changes; }
|
||||
@@ -234,8 +235,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
PaymentDetail paymentDetail = paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
|
||||
|
||||
loan.disburse(currentUser, command, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, changes,
|
||||
paymentDetail);
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, paymentDetail);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.loanRepository.save(loan);
|
||||
|
||||
+8
-7
@@ -61,13 +61,14 @@ public class LoanProductsApiResource {
|
||||
|
||||
private final Set<String> LOAN_PRODUCT_DATA_PARAMETERS = new HashSet<String>(Arrays.asList("id", "name", "description", "fundId",
|
||||
"fundName", "currency", "principal", "minPrincipal", "maxPrincipal", "numberOfRepayments", "minNumberOfRepayments",
|
||||
"maxNumberOfRepayments", "repaymentEvery", "repaymentFrequencyType", "interestRatePerPeriod", "minInterestRatePerPeriod",
|
||||
"maxInterestRatePerPeriod", "interestRateFrequencyType", "annualInterestRate", "amortizationType", "interestType",
|
||||
"interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "transactionProcessingStrategyName",
|
||||
"charges", "accountingRule", "accountingMappings", "paymentChannelToFundSourceMappings", "fundOptions", "paymentTypeOptions",
|
||||
"currencyOptions", "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "amortizationTypeOptions",
|
||||
"interestTypeOptions", "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions",
|
||||
"accountingOptions", "accountingRuleOptions", "accountingMappingOptions"));
|
||||
"maxNumberOfRepayments", "repaymentEvery", "repaymentFrequencyType", "graceOnPrincipalPayment", "graceOnInterestPayment",
|
||||
"graceOnInterestCharged", "interestRatePerPeriod", "minInterestRatePerPeriod", "maxInterestRatePerPeriod",
|
||||
"interestRateFrequencyType", "annualInterestRate", "amortizationType", "interestType", "interestCalculationPeriodType",
|
||||
"inArrearsTolerance", "transactionProcessingStrategyId", "transactionProcessingStrategyName", "charges", "accountingRule",
|
||||
"accountingMappings", "paymentChannelToFundSourceMappings", "fundOptions", "paymentTypeOptions", "currencyOptions",
|
||||
"repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "amortizationTypeOptions", "interestTypeOptions",
|
||||
"interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions",
|
||||
"accountingRuleOptions", "accountingMappingOptions"));
|
||||
|
||||
private final String resourceNameForPermissions = "LOANPRODUCT";
|
||||
|
||||
|
||||
+42
-3
@@ -60,6 +60,9 @@ public class LoanProductData {
|
||||
private final BigDecimal inArrearsTolerance;
|
||||
private final Long transactionProcessingStrategyId;
|
||||
private final String transactionProcessingStrategyName;
|
||||
private final Integer graceOnPrincipalPayment;
|
||||
private final Integer graceOnInterestPayment;
|
||||
private final Integer graceOnInterestCharged;
|
||||
|
||||
// charges
|
||||
private final Collection<ChargeData> charges;
|
||||
@@ -112,6 +115,9 @@ public class LoanProductData {
|
||||
final String fundName = null;
|
||||
final Long transactionProcessingStrategyId = null;
|
||||
final String transactionProcessingStrategyName = null;
|
||||
final Integer graceOnPrincipalPayment = null;
|
||||
final Integer graceOnInterestPayment = null;
|
||||
final Integer graceOnInterestCharged = null;
|
||||
final Collection<ChargeData> charges = null;
|
||||
final EnumOptionData accountingType = null;
|
||||
|
||||
@@ -119,7 +125,8 @@ public class LoanProductData {
|
||||
minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod,
|
||||
maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
|
||||
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
|
||||
transactionProcessingStrategyName, charges, accountingType);
|
||||
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
|
||||
accountingType);
|
||||
}
|
||||
|
||||
public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) {
|
||||
@@ -145,6 +152,10 @@ public class LoanProductData {
|
||||
final String fundName = null;
|
||||
final Long transactionProcessingStrategyId = null;
|
||||
final String transactionProcessingStrategyName = null;
|
||||
final Integer graceOnPrincipalPayment = null;
|
||||
final Integer graceOnInterestPayment = null;
|
||||
final Integer graceOnInterestCharged = null;
|
||||
|
||||
final Collection<ChargeData> charges = null;
|
||||
final EnumOptionData accountingType = null;
|
||||
|
||||
@@ -152,7 +163,8 @@ public class LoanProductData {
|
||||
minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod,
|
||||
maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
|
||||
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
|
||||
transactionProcessingStrategyName, charges, accountingType);
|
||||
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
|
||||
accountingType);
|
||||
}
|
||||
|
||||
public static LoanProductData sensibleDefaultsForNewLoanProductCreation() {
|
||||
@@ -167,6 +179,7 @@ public class LoanProductData {
|
||||
final Integer numberOfRepayments = null;
|
||||
final Integer minNumberOfRepayments = null;
|
||||
final Integer maxNumberOfRepayments = null;
|
||||
|
||||
final Integer repaymentEvery = null;
|
||||
final BigDecimal interestRatePerPeriod = null;
|
||||
final BigDecimal minInterestRatePerPeriod = null;
|
||||
@@ -182,6 +195,11 @@ public class LoanProductData {
|
||||
final String fundName = null;
|
||||
final Long transactionProcessingStrategyId = null;
|
||||
final String transactionProcessingStrategyName = null;
|
||||
|
||||
final Integer graceOnPrincipalPayment = null;
|
||||
final Integer graceOnInterestPayment = null;
|
||||
final Integer graceOnInterestCharged = null;
|
||||
|
||||
final Collection<ChargeData> charges = null;
|
||||
final EnumOptionData accountingType = AccountingEnumerations.accountingRuleType(AccountingRuleType.NONE);
|
||||
|
||||
@@ -189,7 +207,8 @@ public class LoanProductData {
|
||||
minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod,
|
||||
maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType,
|
||||
interestType, interestCalculationPeriodType, fundId, fundName, transactionProcessingStrategyId,
|
||||
transactionProcessingStrategyName, charges, accountingType);
|
||||
transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges,
|
||||
accountingType);
|
||||
}
|
||||
|
||||
public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings,
|
||||
@@ -210,6 +229,7 @@ public class LoanProductData {
|
||||
final EnumOptionData interestRateFrequencyType, final EnumOptionData amortizationType, final EnumOptionData interestType,
|
||||
final EnumOptionData interestCalculationPeriodType, final Long fundId, final String fundName,
|
||||
final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName,
|
||||
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
|
||||
final Collection<ChargeData> charges, final EnumOptionData accountingType) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
@@ -222,6 +242,9 @@ public class LoanProductData {
|
||||
this.numberOfRepayments = numberOfRepayments;
|
||||
this.minNumberOfRepayments = minNumberOfRepayments;
|
||||
this.maxNumberOfRepayments = maxNumberOfRepayments;
|
||||
this.graceOnPrincipalPayment = graceOnPrincipalPayment;
|
||||
this.graceOnInterestPayment = graceOnInterestPayment;
|
||||
this.graceOnInterestCharged = graceOnInterestCharged;
|
||||
this.repaymentEvery = repaymentEvery;
|
||||
this.interestRatePerPeriod = interestRatePerPeriod;
|
||||
this.minInterestRatePerPeriod = minInterestRatePerPeriod;
|
||||
@@ -308,6 +331,10 @@ public class LoanProductData {
|
||||
this.transactionProcessingStrategyName = productData.transactionProcessingStrategyName;
|
||||
}
|
||||
|
||||
this.graceOnPrincipalPayment = productData.graceOnPrincipalPayment;
|
||||
this.graceOnInterestPayment = productData.graceOnInterestPayment;
|
||||
this.graceOnInterestCharged = productData.graceOnInterestCharged;
|
||||
|
||||
this.amortizationTypeOptions = amortizationTypeOptions;
|
||||
this.interestTypeOptions = interestTypeOptions;
|
||||
this.interestCalculationPeriodTypeOptions = interestCalculationPeriodTypeOptions;
|
||||
@@ -418,6 +445,18 @@ public class LoanProductData {
|
||||
return repaymentFrequencyType;
|
||||
}
|
||||
|
||||
public Integer getGraceOnPrincipalPayment() {
|
||||
return this.graceOnPrincipalPayment;
|
||||
}
|
||||
|
||||
public Integer getGraceOnInterestPayment() {
|
||||
return this.graceOnInterestPayment;
|
||||
}
|
||||
|
||||
public Integer getGraceOnInterestCharged() {
|
||||
return this.graceOnInterestCharged;
|
||||
}
|
||||
|
||||
public EnumOptionData getInterestRateFrequencyType() {
|
||||
return interestRateFrequencyType;
|
||||
}
|
||||
|
||||
+18
-7
@@ -104,12 +104,19 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
final Integer minNumberOfRepayments = command.integerValueOfParameterNamed("minNumberOfRepayments");
|
||||
final Integer maxNumberOfRepayments = command.integerValueOfParameterNamed("maxNumberOfRepayments");
|
||||
final BigDecimal inArrearsTolerance = command.bigDecimalValueOfParameterNamed("inArrearsTolerance");
|
||||
|
||||
// grace details
|
||||
final Integer graceOnPrincipalPayment = command.integerValueOfParameterNamed("graceOnPrincipalPayment");
|
||||
final Integer graceOnInterestPayment = command.integerValueOfParameterNamed("graceOnInterestPayment");
|
||||
final Integer graceOnInterestCharged = command.integerValueOfParameterNamed("graceOnInterestCharged");
|
||||
|
||||
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
|
||||
|
||||
return new LoanProduct(fund, loanTransactionProcessingStrategy, name, description, currency, principal, minPrincipal, maxPrincipal,
|
||||
interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate,
|
||||
interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, numberOfRepayments,
|
||||
minNumberOfRepayments, maxNumberOfRepayments, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType);
|
||||
minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
|
||||
amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType);
|
||||
}
|
||||
|
||||
protected LoanProduct() {
|
||||
@@ -126,6 +133,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery,
|
||||
final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments,
|
||||
final Integer defaultMinNumberOfInstallments, final Integer defaultMaxNumberOfInstallments,
|
||||
final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged,
|
||||
final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List<Charge> charges,
|
||||
final AccountingRuleType accountingRuleType) {
|
||||
this.fund = fund;
|
||||
@@ -143,7 +151,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
|
||||
this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
|
||||
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery,
|
||||
repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod, inArrearsTolerance);
|
||||
repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, amortizationMethod, inArrearsTolerance);
|
||||
|
||||
this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings();
|
||||
|
||||
this.loanProductMinMaxConstraints = new LoanProductMinMaxConstraints(defaultMinPrincipal, defaultMaxPrincipal,
|
||||
defaultMinNominalInterestRatePerPeriod, defaultMaxNominalInterestRatePerPeriod, defaultMinNumberOfInstallments,
|
||||
@@ -274,6 +285,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
return this.loanProductRelatedDetail.getNominalInterestRatePerPeriod();
|
||||
}
|
||||
|
||||
public PeriodFrequencyType getInterestPeriodFrequencyType() {
|
||||
return this.loanProductRelatedDetail.getInterestPeriodFrequencyType();
|
||||
}
|
||||
|
||||
public BigDecimal getMinNominalInterestRatePerPeriod() {
|
||||
return this.loanProductMinMaxConstraints().getMinNominalInterestRatePerPeriod();
|
||||
}
|
||||
@@ -283,7 +298,7 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
public Integer getNumberOfRepayments() {
|
||||
return this.loanProductRelatedDetail().getNumberOfRepayments();
|
||||
return this.loanProductRelatedDetail.getNumberOfRepayments();
|
||||
}
|
||||
|
||||
public Integer getMinNumberOfRepayments() {
|
||||
@@ -294,10 +309,6 @@ public class LoanProduct extends AbstractPersistable<Long> {
|
||||
return this.loanProductMinMaxConstraints().getMaxNumberOfRepayments();
|
||||
}
|
||||
|
||||
public LoanProductRelatedDetail loanProductRelatedDetail() {
|
||||
return this.loanProductRelatedDetail;
|
||||
}
|
||||
|
||||
public LoanProductMinMaxConstraints loanProductMinMaxConstraints() {
|
||||
// If all min and max fields are null then loanProductMinMaxConstraints
|
||||
// initialising to null
|
||||
|
||||
+92
-8
@@ -6,7 +6,9 @@
|
||||
package org.mifosplatform.portfolio.loanproduct.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.Column;
|
||||
@@ -16,6 +18,9 @@ import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
|
||||
@@ -33,7 +38,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
|
||||
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = false)
|
||||
private BigDecimal principal;
|
||||
|
||||
|
||||
@Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = false)
|
||||
private BigDecimal nominalInterestRatePerPeriod;
|
||||
|
||||
@@ -69,7 +74,16 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
|
||||
@Column(name = "number_of_repayments", nullable = false)
|
||||
private Integer numberOfRepayments;
|
||||
|
||||
|
||||
@Column(name = "grace_on_principal_periods", nullable = true)
|
||||
private Integer graceOnPrincipalPayment;
|
||||
|
||||
@Column(name = "grace_on_interest_periods", nullable = true)
|
||||
private Integer graceOnInterestPayment;
|
||||
|
||||
@Column(name = "grace_interest_free_periods", nullable = true)
|
||||
private Integer graceOnInterestCharged;
|
||||
|
||||
// FIXME - move away form JPA ordinal use for enums using just integer -
|
||||
// requires sql patch for existing users of software.
|
||||
@Enumerated(EnumType.ORDINAL)
|
||||
@@ -84,11 +98,13 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
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) {
|
||||
|
||||
return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType,
|
||||
nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentPeriodFrequencyType,
|
||||
numberOfRepayments, amortizationMethod, inArrearsTolerance);
|
||||
numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
||||
inArrearsTolerance);
|
||||
}
|
||||
|
||||
protected LoanProductRelatedDetail() {
|
||||
@@ -96,10 +112,11 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
}
|
||||
|
||||
public LoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
|
||||
final BigDecimal defaultNominalInterestRatePerPeriod,final PeriodFrequencyType interestPeriodFrequencyType,
|
||||
final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType,
|
||||
final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
|
||||
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) {
|
||||
this.currency = currency;
|
||||
this.principal = defaultPrincipal;
|
||||
@@ -111,6 +128,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
this.repayEvery = repayEvery;
|
||||
this.repaymentPeriodFrequencyType = repaymentFrequencyType;
|
||||
this.numberOfRepayments = defaultNumberOfRepayments;
|
||||
this.graceOnPrincipalPayment = defaultToNullIfZero(graceOnPrincipalPayment);
|
||||
this.graceOnInterestPayment = defaultToNullIfZero(graceOnInterestPayment);
|
||||
this.graceOnInterestCharged = defaultToNullIfZero(graceOnInterestCharged);
|
||||
this.amortizationMethod = amortizationMethod;
|
||||
if (inArrearsTolerance != null && BigDecimal.ZERO.compareTo(inArrearsTolerance) == 0) {
|
||||
this.inArrearsTolerance = null;
|
||||
@@ -119,6 +139,14 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
}
|
||||
}
|
||||
|
||||
private Integer defaultToNullIfZero(final Integer value) {
|
||||
Integer defaultTo = value;
|
||||
if (value != null && Integer.valueOf(0).equals(value)) {
|
||||
defaultTo = null;
|
||||
}
|
||||
return defaultTo;
|
||||
}
|
||||
|
||||
public MonetaryCurrency getCurrency() {
|
||||
return this.currency.copy();
|
||||
}
|
||||
@@ -126,7 +154,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
public Money getPrincipal() {
|
||||
return Money.of(this.currency, this.principal);
|
||||
}
|
||||
|
||||
|
||||
public Money getInArrearsTolerance() {
|
||||
return Money.of(this.currency, this.inArrearsTolerance);
|
||||
}
|
||||
@@ -165,7 +193,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
public Integer getNumberOfRepayments() {
|
||||
return numberOfRepayments;
|
||||
}
|
||||
|
||||
|
||||
public AmortizationMethod getAmortizationMethod() {
|
||||
return amortizationMethod;
|
||||
}
|
||||
@@ -231,7 +259,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(newValue);
|
||||
}
|
||||
|
||||
|
||||
final String numberOfRepaymentsParamName = "numberOfRepayments";
|
||||
if (command.isChangeInIntegerParameterNamed(numberOfRepaymentsParamName, this.numberOfRepayments)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(numberOfRepaymentsParamName);
|
||||
@@ -255,7 +283,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.inArrearsTolerance = newValue;
|
||||
}
|
||||
|
||||
|
||||
final String interestRatePerPeriodParamName = "interestRatePerPeriod";
|
||||
if (command.isChangeInBigDecimalParameterNamed(interestRatePerPeriodParamName, this.nominalInterestRatePerPeriod)) {
|
||||
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRatePerPeriodParamName);
|
||||
@@ -291,9 +319,65 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
|
||||
this.interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(newValue);
|
||||
}
|
||||
|
||||
final String graceOnPrincipalPaymentParamName = "graceOnPrincipalPayment";
|
||||
if (command.isChangeInIntegerParameterNamed(graceOnPrincipalPaymentParamName, this.graceOnPrincipalPayment)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(graceOnPrincipalPaymentParamName);
|
||||
actualChanges.put(graceOnPrincipalPaymentParamName, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.graceOnPrincipalPayment = newValue;
|
||||
}
|
||||
|
||||
final String graceOnInterestPaymentParamName = "graceOnInterestPayment";
|
||||
if (command.isChangeInIntegerParameterNamed(graceOnInterestPaymentParamName, this.graceOnInterestPayment)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(graceOnInterestPaymentParamName);
|
||||
actualChanges.put(graceOnInterestPaymentParamName, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.graceOnInterestPayment = newValue;
|
||||
}
|
||||
|
||||
final String graceOnInterestChargedParamName = "graceOnInterestCharged";
|
||||
if (command.isChangeInIntegerParameterNamed(graceOnInterestChargedParamName, this.graceOnInterestCharged)) {
|
||||
final Integer newValue = command.integerValueOfParameterNamed(graceOnInterestChargedParamName);
|
||||
actualChanges.put(graceOnInterestChargedParamName, newValue);
|
||||
actualChanges.put("locale", localeAsInput);
|
||||
this.graceOnInterestCharged = newValue;
|
||||
}
|
||||
|
||||
validateRepaymentPeriodWithGraceSettings();
|
||||
|
||||
return actualChanges;
|
||||
}
|
||||
|
||||
public void validateRepaymentPeriodWithGraceSettings() {
|
||||
final List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
|
||||
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loanproduct");
|
||||
|
||||
if (this.numberOfRepayments <= defaultToZeroIfNull(this.graceOnPrincipalPayment)) {
|
||||
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment)
|
||||
.failWithCode(".mustBeLessThan.numberOfRepayments");
|
||||
}
|
||||
|
||||
if (this.numberOfRepayments <= defaultToZeroIfNull(this.graceOnInterestPayment)) {
|
||||
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment)
|
||||
.failWithCode(".mustBeLessThan.numberOfRepayments");
|
||||
}
|
||||
|
||||
if (this.numberOfRepayments < defaultToZeroIfNull(this.graceOnInterestCharged)) {
|
||||
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged)
|
||||
.failWithCode(".mustBeLessThan.numberOfRepayments");
|
||||
}
|
||||
|
||||
if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
|
||||
}
|
||||
|
||||
private Integer defaultToZeroIfNull(final Integer value) {
|
||||
Integer result = value;
|
||||
if (value == null) {
|
||||
result = Integer.valueOf(0);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void updateInterestRateDerivedFields(final AprCalculator aprCalculator) {
|
||||
this.annualNominalInterestRate = aprCalculator.calculateFrom(this.interestPeriodFrequencyType, this.nominalInterestRatePerPeriod);
|
||||
}
|
||||
|
||||
+33
-8
@@ -16,8 +16,8 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.mifosplatform.accounting.common.AccountingRuleType;
|
||||
import org.mifosplatform.accounting.common.AccountingConstants.LOAN_PRODUCT_ACCOUNTING_PARAMS;
|
||||
import org.mifosplatform.accounting.common.AccountingRuleType;
|
||||
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.mifosplatform.infrastructure.core.exception.InvalidJsonException;
|
||||
@@ -33,7 +33,7 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
@Component
|
||||
public final class LoanProductCommandFromApiJsonDeserializer {
|
||||
public final class LoanProductDataValidator {
|
||||
|
||||
/**
|
||||
* The parameters supported for this command.
|
||||
@@ -42,7 +42,8 @@ public final class LoanProductCommandFromApiJsonDeserializer {
|
||||
"currencyCode", "digitsAfterDecimal", "principal", "minPrincipal", "maxPrincipal", "repaymentEvery", "numberOfRepayments",
|
||||
"minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod",
|
||||
"minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType",
|
||||
"interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "charges", "accountingRule",
|
||||
"interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "graceOnPrincipalPayment",
|
||||
"graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule",
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(),
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(),
|
||||
LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(),
|
||||
@@ -53,7 +54,7 @@ public final class LoanProductCommandFromApiJsonDeserializer {
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Autowired
|
||||
public LoanProductCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) {
|
||||
public LoanProductDataValidator(final FromJsonHelper fromApiJsonHelper) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
}
|
||||
|
||||
@@ -218,6 +219,16 @@ public final class LoanProductCommandFromApiJsonDeserializer {
|
||||
baseDataValidator.reset().parameter("transactionProcessingStrategyId").value(transactionProcessingStrategyId).notNull()
|
||||
.integerGreaterThanZero();
|
||||
|
||||
// grace validation
|
||||
final Integer graceOnPrincipalPayment = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
||||
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
|
||||
|
||||
final Integer graceOnInterestPayment = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
||||
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment).zeroOrPositiveAmount();
|
||||
|
||||
final Integer graceOnInterestCharged = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
|
||||
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged).zeroOrPositiveAmount();
|
||||
|
||||
// accounting related data validation
|
||||
final Integer accountingRuleType = fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
|
||||
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
|
||||
@@ -396,6 +407,23 @@ public final class LoanProductCommandFromApiJsonDeserializer {
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
|
||||
// grace validation
|
||||
if (fromApiJsonHelper.parameterExists("graceOnPrincipalPayment", element)) {
|
||||
final Integer graceOnPrincipalPayment = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
|
||||
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
if (fromApiJsonHelper.parameterExists("graceOnInterestPayment", element)) {
|
||||
final Integer graceOnInterestPayment = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
|
||||
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment).zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
if (fromApiJsonHelper.parameterExists("graceOnInterestCharged", element)) {
|
||||
final Integer graceOnInterestCharged = fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
|
||||
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged).zeroOrPositiveAmount();
|
||||
}
|
||||
|
||||
//
|
||||
if (fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)) {
|
||||
final Integer interestRateFrequencyType = fromApiJsonHelper.extractIntegerNamed("interestRateFrequencyType", element,
|
||||
Locale.getDefault());
|
||||
@@ -473,11 +501,8 @@ public final class LoanProductCommandFromApiJsonDeserializer {
|
||||
throwExceptionIfValidationWarningsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* Validation for advanced accounting options
|
||||
*
|
||||
* @param baseDataValidator
|
||||
* @param element
|
||||
*/
|
||||
private void validatePaymentChannelFundSourceMappings(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
|
||||
if (fromApiJsonHelper.parameterExists(LOAN_PRODUCT_ACCOUNTING_PARAMS.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(), element)) {
|
||||
+7
-1
@@ -101,6 +101,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
+ "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.min_nominal_interest_rate_per_period as minInterestRatePerPeriod, lp.max_nominal_interest_rate_per_period as maxInterestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, "
|
||||
+ "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,"
|
||||
+ "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, "
|
||||
+ "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,"
|
||||
+ "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, "
|
||||
+ "lp.accounting_type as accountingType, "
|
||||
+ "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol "
|
||||
@@ -140,6 +141,10 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
final Integer maxNumberOfRepayments = JdbcSupport.getInteger(rs, "maxNumberOfRepayments");
|
||||
final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaidEvery");
|
||||
|
||||
final Integer graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment");
|
||||
final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment");
|
||||
final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged");
|
||||
|
||||
final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
|
||||
final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
|
||||
|
||||
@@ -168,7 +173,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
|
||||
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
|
||||
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
|
||||
interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, fundId, fundName,
|
||||
transactionStrategyId, transactionStrategyName, this.charges, accountingRuleType);
|
||||
transactionStrategyId, transactionStrategyName, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, this.charges, accountingRuleType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-3
@@ -28,7 +28,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
|
||||
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
|
||||
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
|
||||
import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductCommandFromApiJsonDeserializer;
|
||||
import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductDataValidator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -44,7 +44,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(LoanProductWritePlatformServiceJpaRepositoryImpl.class);
|
||||
private final PlatformSecurityContext context;
|
||||
private final LoanProductCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
private final LoanProductDataValidator fromApiJsonDeserializer;
|
||||
private final LoanProductRepository loanProductRepository;
|
||||
private final AprCalculator aprCalculator;
|
||||
private final FundRepository fundRepository;
|
||||
@@ -54,7 +54,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
|
||||
|
||||
@Autowired
|
||||
public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
|
||||
final LoanProductCommandFromApiJsonDeserializer fromApiJsonDeserializer, final LoanProductRepository loanProductRepository,
|
||||
final LoanProductDataValidator fromApiJsonDeserializer, final LoanProductRepository loanProductRepository,
|
||||
final AprCalculator aprCalculator, final FundRepository fundRepository,
|
||||
final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
|
||||
final ChargeRepositoryWrapper chargeRepository,
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.domain.LocalDateInterval;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ import org.joda.time.MonthDay;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||
import org.mifosplatform.infrastructure.core.data.ApiParameterError;
|
||||
import org.mifosplatform.infrastructure.core.domain.LocalDateInterval;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.mifosplatform.infrastructure.core.service.DateUtils;
|
||||
import org.mifosplatform.infrastructure.security.service.RandomPasswordGenerator;
|
||||
|
||||
+1
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.domain.LocalDateInterval;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.domain.LocalDateInterval;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
|
||||
+5
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.portfolio.savings.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
-->
|
||||
<!--
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
|
||||
-140
@@ -1,140 +0,0 @@
|
||||
package org.mifosplatform.portfolio.loanaccount;
|
||||
|
||||
//import static org.hamcrest.CoreMatchers.*;
|
||||
//import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceMethodLoanScheduleGenerator;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DecliningBalanceMethodLoanScheduleGeneratorTest {
|
||||
|
||||
// class under test
|
||||
private DecliningBalanceMethodLoanScheduleGenerator decliningBalanceGenerator;
|
||||
|
||||
// test doubles
|
||||
private CurrencyData currencyData = null;
|
||||
private LoanProductRelatedDetail loanScheduleInfo = null;
|
||||
|
||||
@Before
|
||||
public void setupForEachTest() {
|
||||
currencyData = new CurrencyData("USD", "US Dollar", 2, "$", "currency.USD");
|
||||
|
||||
decliningBalanceGenerator = new DecliningBalanceMethodLoanScheduleGenerator();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoid() {
|
||||
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void givenEqualInstallmentAmortisationShouldCalculatePaymentPerInstallmentUsingPmtFunctionAndDerivePrincipalAndInterestComponents() {
|
||||
//
|
||||
// // setup
|
||||
// Integer loanTermFrequency = 12;
|
||||
// PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
|
||||
// loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForEqualInstallmentAmortizationQuarterly();
|
||||
//
|
||||
// LocalDate disbursementDate = new LocalDate(2011, 1, 11);
|
||||
// LocalDate firstRepaymentDate = new LocalDate(2011, 5, 1);
|
||||
// LocalDate interestCalculatedFrom = new LocalDate(2011, 2, 1);
|
||||
//
|
||||
// // pre assertions
|
||||
// assertThat(loanTermFrequency, is(loanScheduleInfo.getNumberOfRepayments() * loanScheduleInfo.getRepayEvery()));
|
||||
// assertThat(loanTermFrequencyType, is(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
|
||||
//
|
||||
// // exercise test
|
||||
// LoanSchedule schedule = decliningBalanceGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
|
||||
// disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
|
||||
//
|
||||
// // verification (post assertions)
|
||||
// given(schedule).assertHasInstallmentSizeOf(4);
|
||||
// given(schedule).assertTotalPaymentDueOf(1, is("57718.30")).assertInterestDueComponentOf(1, is("12000.00")).assertPrincipalDueComponentOf(1, is("45718.30"));
|
||||
// given(schedule).assertTotalPaymentDueOf(2, is("57718.30")).assertInterestDueComponentOf(2, is("9256.90")).assertPrincipalDueComponentOf(2, is("48461.40"));
|
||||
// given(schedule).assertTotalPaymentDueOf(3, is("57718.30")).assertInterestDueComponentOf(3, is("6349.22")).assertPrincipalDueComponentOf(3, is("51369.08"));
|
||||
// given(schedule).assertTotalPaymentDueOf(4, is("57718.29")).assertInterestDueComponentOf(4, is("3267.07")).assertPrincipalDueComponentOf(4, is("54451.22"));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void givenEqualPrincipalAmortisationShouldCalculateInterestComponentFromOutstandingPrincipalBlanceResultingInDecliningInterestAndTotalRepayments() {
|
||||
//
|
||||
// // setup
|
||||
// Integer loanTermFrequency = 12;
|
||||
// PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
|
||||
// loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForEqualPrincipalAmortizationQuarterly();
|
||||
//
|
||||
// LocalDate disbursementDate = new LocalDate(2011, 1, 11);
|
||||
// LocalDate firstRepaymentDate = new LocalDate(2011, 5, 1);
|
||||
// LocalDate interestCalculatedFrom = new LocalDate(2011, 2, 1);
|
||||
//
|
||||
// // pre assertions
|
||||
// assertThat(loanTermFrequency, is(loanScheduleInfo.getNumberOfRepayments() * loanScheduleInfo.getRepayEvery()));
|
||||
// assertThat(loanTermFrequencyType, is(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
|
||||
//
|
||||
// // exercise test
|
||||
// LoanSchedule schedule = decliningBalanceGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
|
||||
// disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
|
||||
//
|
||||
// // verification (post assertions)
|
||||
// given(schedule).assertHasInstallmentSizeOf(4);
|
||||
// given(schedule).assertPrincipalDueComponentOf(1, is("50000.00")).assertInterestDueComponentOf(1, is("12000.00")).assertTotalPaymentDueOf(1, is("62000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(2, is("50000.00")).assertInterestDueComponentOf(2, is("9000.00")).assertTotalPaymentDueOf(2, is("59000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(3, is("50000.00")).assertInterestDueComponentOf(3, is("6000.00")).assertTotalPaymentDueOf(3, is("56000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(4, is("50000.00")).assertInterestDueComponentOf(4, is("3000.00")).assertTotalPaymentDueOf(4, is("53000.00"));
|
||||
// }
|
||||
//
|
||||
// private LoanScheduleAssertionBuilder given(final LoanSchedule schedule) {
|
||||
// return new LoanScheduleAssertionBuilder(schedule);
|
||||
// }
|
||||
//
|
||||
// private class LoanScheduleAssertionBuilder {
|
||||
//
|
||||
// private final LoanSchedule schedule;
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder(final LoanSchedule schedule) {
|
||||
// this.schedule = schedule;
|
||||
// }
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder assertPrincipalDueComponentOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
|
||||
// final Integer installmentIndex = installmentNumber -1;
|
||||
// List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
|
||||
// ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
|
||||
//
|
||||
// assertThat("Expected principal due component for installment " + installmentNumber + " is wrong: ",
|
||||
// repaymentInstallment.getPrincipalDue().toString(), matchesExpectedAmount);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder assertInterestDueComponentOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
|
||||
// final Integer installmentIndex = installmentNumber -1;
|
||||
// List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
|
||||
// ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
|
||||
//
|
||||
// assertThat("Expected interest due component for installment " + installmentNumber + " is wrong: ",
|
||||
// repaymentInstallment.getInterestDue().toString(), matchesExpectedAmount);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder assertTotalPaymentDueOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
|
||||
// final Integer installmentIndex = installmentNumber -1;
|
||||
// List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
|
||||
// ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
|
||||
//
|
||||
// assertThat("Expected total payment for installment " + installmentNumber + " is wrong: ",
|
||||
// repaymentInstallment.getTotalInstallmentDue().toString(), matchesExpectedAmount);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder assertHasInstallmentSizeOf(int expectedInstallmentSize) {
|
||||
// assertThat("Expected number of installments is wrong: ", schedule.getScheduledLoanInstallments().size(), is(expectedInstallmentSize));
|
||||
// return this;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
-163
@@ -1,163 +0,0 @@
|
||||
package org.mifosplatform.portfolio.loanaccount;
|
||||
|
||||
//import static org.hamcrest.CoreMatchers.*;
|
||||
//import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mifosplatform.organisation.monetary.data.CurrencyData;
|
||||
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.FlatMethodLoanScheduleGenerator;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class FlatMethodLoanScheduleGeneratorTest {
|
||||
|
||||
// class under test
|
||||
private FlatMethodLoanScheduleGenerator flatLoanScheduleGenerator;
|
||||
|
||||
// test doubles
|
||||
private CurrencyData currencyData = null;
|
||||
private LoanProductRelatedDetail loanScheduleInfo = null;
|
||||
|
||||
@Before
|
||||
public void setupForEachTest() {
|
||||
currencyData = new CurrencyData("USD", "US Dollar", 2, "$", "currency.USD");
|
||||
|
||||
flatLoanScheduleGenerator = new FlatMethodLoanScheduleGenerator();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rewriteAllTests() {
|
||||
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void givenFlatMethodShouldCalculateFlatInterestComponentToDerivePrincipalAndTotalRepaymentRegardlessOfEqualInstallmentAmortisation() {
|
||||
//
|
||||
// // setup
|
||||
// Integer loanTermFrequency = 12;
|
||||
// PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
|
||||
// loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForFlatQuarterly(AmortizationMethod.EQUAL_INSTALLMENTS);
|
||||
//
|
||||
// LocalDate disbursementDate = new LocalDate(2011, 1, 11);
|
||||
// LocalDate firstRepaymentDate = new LocalDate(2011, 5, 1);
|
||||
// LocalDate interestCalculatedFrom = new LocalDate(2011, 2, 1);
|
||||
//
|
||||
// // pre assertions
|
||||
// assertThat(loanTermFrequency, is(loanScheduleInfo.getNumberOfRepayments() * loanScheduleInfo.getRepayEvery()));
|
||||
// assertThat(loanTermFrequencyType, is(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
|
||||
//
|
||||
// // exercise test
|
||||
// LoanSchedule schedule = flatLoanScheduleGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
|
||||
// disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
|
||||
//
|
||||
// // verification (post assertions)
|
||||
// given(schedule).assertHasInstallmentSizeOf(4);
|
||||
// given(schedule).assertPrincipalDueComponentOf(1, is("50000.00")).assertInterestDueComponentOf(1, is("12000.00")).assertTotalPaymentDueOf(1, is("62000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(2, is("50000.00")).assertInterestDueComponentOf(2, is("12000.00")).assertTotalPaymentDueOf(2, is("62000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(3, is("50000.00")).assertInterestDueComponentOf(3, is("12000.00")).assertTotalPaymentDueOf(3, is("62000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(4, is("50000.00")).assertInterestDueComponentOf(4, is("12000.00")).assertTotalPaymentDueOf(4, is("62000.00"));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void givenFlatMethodShouldCalculateFlatInterestComponentToDerivePrincipalAndTotalRepaymentRegardlessOfEqualPrincipalAmortisation() {
|
||||
//
|
||||
// // setup
|
||||
// Integer loanTermFrequency = 12;
|
||||
// PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
|
||||
// loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForFlatQuarterly(AmortizationMethod.EQUAL_PRINCIPAL);
|
||||
//
|
||||
// LocalDate disbursementDate = new LocalDate(2011, 1, 11);
|
||||
// LocalDate firstRepaymentDate = new LocalDate(2011, 5, 1);
|
||||
// LocalDate interestCalculatedFrom = new LocalDate(2011, 2, 1);
|
||||
//
|
||||
// // pre assertions
|
||||
// assertThat(loanTermFrequency, is(loanScheduleInfo.getNumberOfRepayments() * loanScheduleInfo.getRepayEvery()));
|
||||
// assertThat(loanTermFrequencyType, is(loanScheduleInfo.getRepaymentPeriodFrequencyType()));
|
||||
//
|
||||
// // exercise test
|
||||
// LoanSchedule schedule = flatLoanScheduleGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
|
||||
// disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
|
||||
//
|
||||
// // verification (post assertions)
|
||||
// given(schedule).assertHasInstallmentSizeOf(4);
|
||||
// given(schedule).assertPrincipalDueComponentOf(1, is("50000.00")).assertInterestDueComponentOf(1, is("12000.00")).assertTotalPaymentDueOf(1, is("62000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(2, is("50000.00")).assertInterestDueComponentOf(2, is("12000.00")).assertTotalPaymentDueOf(2, is("62000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(3, is("50000.00")).assertInterestDueComponentOf(3, is("12000.00")).assertTotalPaymentDueOf(3, is("62000.00"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(4, is("50000.00")).assertInterestDueComponentOf(4, is("12000.00")).assertTotalPaymentDueOf(4, is("62000.00"));
|
||||
// }
|
||||
//
|
||||
// @Test
|
||||
// public void givenFlatMethodAndIrregularFirstRepaymentShouldCalculateFlatInterestComponent() {
|
||||
//
|
||||
// // setup
|
||||
// currencyData = new CurrencyData("KSH", "Keyan Shilling", 0, "KSh", "currency.KSH");
|
||||
// Integer loanTermFrequency = 9;
|
||||
// PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.MONTHS;
|
||||
// loanScheduleInfo = LoanProductRelatedDetailTestHelper.createSettingsForIrregularFlatEveryFourMonths();
|
||||
//
|
||||
// LocalDate disbursementDate = new LocalDate(2012, 3, 27);
|
||||
// LocalDate firstRepaymentDate = new LocalDate(2011, 7, 27);
|
||||
// LocalDate interestCalculatedFrom = disbursementDate;
|
||||
//
|
||||
// // exercise test
|
||||
// LoanSchedule schedule = flatLoanScheduleGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType,
|
||||
// disbursementDate, firstRepaymentDate, interestCalculatedFrom, currencyData);
|
||||
//
|
||||
// // verification (post assertions)
|
||||
// given(schedule).assertHasInstallmentSizeOf(2);
|
||||
// given(schedule).assertPrincipalDueComponentOf(1, is("7500")).assertInterestDueComponentOf(1, is("1350")).assertTotalPaymentDueOf(1, is("8850"));
|
||||
// given(schedule).assertPrincipalDueComponentOf(2, is("7500")).assertInterestDueComponentOf(2, is("1350")).assertTotalPaymentDueOf(2, is("8850"));
|
||||
// }
|
||||
//
|
||||
// private LoanScheduleAssertionBuilder given(final LoanSchedule schedule) {
|
||||
// return new LoanScheduleAssertionBuilder(schedule);
|
||||
// }
|
||||
//
|
||||
// private class LoanScheduleAssertionBuilder {
|
||||
//
|
||||
// private final LoanSchedule schedule;
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder(final LoanSchedule schedule) {
|
||||
// this.schedule = schedule;
|
||||
// }
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder assertPrincipalDueComponentOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
|
||||
// final Integer installmentIndex = installmentNumber -1;
|
||||
// List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
|
||||
// ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
|
||||
//
|
||||
// assertThat("Expected principal due component for installment " + installmentNumber + " is wrong: ",
|
||||
// repaymentInstallment.getPrincipalDue().toString(), matchesExpectedAmount);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder assertInterestDueComponentOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
|
||||
// final Integer installmentIndex = installmentNumber -1;
|
||||
// List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
|
||||
// ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
|
||||
//
|
||||
// assertThat("Expected interest due component for installment " + installmentNumber + " is wrong: ",
|
||||
// repaymentInstallment.getInterestDue().toString(), matchesExpectedAmount);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder assertTotalPaymentDueOf(final int installmentNumber, Matcher<String> matchesExpectedAmount) {
|
||||
// final Integer installmentIndex = installmentNumber -1;
|
||||
// List<ScheduledLoanInstallment> repaymentInstallments = schedule.getScheduledLoanInstallments();
|
||||
// ScheduledLoanInstallment repaymentInstallment = repaymentInstallments.get(installmentIndex);
|
||||
//
|
||||
// assertThat("Expected total payment for installment " + installmentNumber + " is wrong: ",
|
||||
// repaymentInstallment.getTotalInstallmentDue().toString(), matchesExpectedAmount);
|
||||
// return this;
|
||||
// }
|
||||
//
|
||||
// public LoanScheduleAssertionBuilder assertHasInstallmentSizeOf(int expectedInstallmentSize) {
|
||||
// assertThat("Expected number of installments is wrong: ", schedule.getScheduledLoanInstallments().size(), is(expectedInstallmentSize));
|
||||
// return this;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
+13
-8
@@ -70,7 +70,7 @@ public class LoanProductRelatedDetailTestHelper {
|
||||
|
||||
MonetaryCurrency currency = new MonetaryCurrencyBuilder().withCode("USD").withDigitsAfterDecimal(2).build();
|
||||
BigDecimal defaultPrincipal = BigDecimal.valueOf(Double.valueOf("200000"));
|
||||
|
||||
|
||||
// 2% per month, 24% per year
|
||||
BigDecimal defaultNominalInterestRatePerPeriod = BigDecimal.valueOf(Double.valueOf("2"));
|
||||
PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.MONTHS;
|
||||
@@ -116,15 +116,20 @@ public class LoanProductRelatedDetailTestHelper {
|
||||
defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
|
||||
}
|
||||
|
||||
private static LoanProductRelatedDetail createLoanProductRelatedDetail(MonetaryCurrency currency, BigDecimal defaultPrincipal,
|
||||
BigDecimal defaultNominalInterestRatePerPeriod, PeriodFrequencyType interestPeriodFrequencyType,
|
||||
BigDecimal defaultAnnualNominalInterestRate, InterestMethod interestMethod,
|
||||
InterestCalculationPeriodMethod interestCalculationPeriodMethod, Integer repayEvery,
|
||||
PeriodFrequencyType repaymentFrequencyType, Integer defaultNumberOfRepayments, AmortizationMethod amortizationMethod,
|
||||
BigDecimal inArrearsTolerance) {
|
||||
private static LoanProductRelatedDetail createLoanProductRelatedDetail(final MonetaryCurrency currency, final BigDecimal defaultPrincipal,
|
||||
final BigDecimal defaultNominalInterestRatePerPeriod, final PeriodFrequencyType interestPeriodFrequencyType,
|
||||
final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod,
|
||||
final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery,
|
||||
final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments, final AmortizationMethod amortizationMethod,
|
||||
final BigDecimal inArrearsTolerance) {
|
||||
|
||||
final Integer graceOnPrincipalPayment = Integer.valueOf(0);
|
||||
final Integer graceOnInterestPayment = Integer.valueOf(0);
|
||||
final Integer graceOnInterestCharged = Integer.valueOf(0);
|
||||
|
||||
return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType,
|
||||
defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType,
|
||||
defaultNumberOfRepayments, amortizationMethod, inArrearsTolerance);
|
||||
defaultNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod,
|
||||
inArrearsTolerance);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user