mifosx-58: wip - refactoring api for loan schedule data - introduced new schedule in prep for supporting charges.

This commit is contained in:
Keith Woodlock
2012-09-18 15:15:41 +01:00
parent c6317acfae
commit 4084c194a0
18 changed files with 794 additions and 238 deletions
@@ -37,7 +37,6 @@ import org.mifosng.platform.api.data.LoanChargeData;
import org.mifosng.platform.api.data.LoanPermissionData; import org.mifosng.platform.api.data.LoanPermissionData;
import org.mifosng.platform.api.data.LoanProductLookup; import org.mifosng.platform.api.data.LoanProductLookup;
import org.mifosng.platform.api.data.LoanRepaymentTransactionData; import org.mifosng.platform.api.data.LoanRepaymentTransactionData;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.LoanScheduleData; import org.mifosng.platform.api.data.LoanScheduleData;
import org.mifosng.platform.api.data.LoanTransactionData; import org.mifosng.platform.api.data.LoanTransactionData;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.MoneyData;
@@ -273,49 +272,50 @@ public class LoansApiResource {
if (is(commandParam, "calculateLoanSchedule")) { if (is(commandParam, "calculateLoanSchedule")) {
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand(); CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
NewLoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(calculateLoanScheduleCommand); return calculateLoanSchedule(uriInfo, calculateLoanScheduleCommand);
final Set<String> typicalLoanScheduleResponseParameters = new HashSet<String>(
Arrays.asList("periods", "cumulativePrincipalDisbursed"));
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalLoanScheduleResponseParameters);
}
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
return this.apiJsonSerializerService.serializeLoanScheduleDataToJson(prettyPrint, responseParameters, loanSchedule);
} }
final EntityIdentifier identifier = this.loanWritePlatformService.submitLoanApplication(command); final EntityIdentifier identifier = this.loanWritePlatformService.submitLoanApplication(command);
return this.apiJsonSerializerService.serializeEntityIdentifier(identifier); return this.apiJsonSerializerService.serializeEntityIdentifier(identifier);
} }
private String calculateLoanSchedule(final UriInfo uriInfo, final CalculateLoanScheduleCommand command) {
final NewLoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command);
final Set<String> typicalLoanScheduleResponseParameters = new HashSet<String>(
Arrays.asList("periods", "cumulativePrincipalDisbursed"));
Set<String> responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters());
if (responseParameters.isEmpty()) {
responseParameters.addAll(typicalLoanScheduleResponseParameters);
}
boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters());
return this.apiJsonSerializerService.serializeLoanScheduleDataToJson(prettyPrint, responseParameters, loanSchedule);
}
@PUT @PUT
@Path("{loanId}") @Path("{loanId}")
@Consumes({MediaType.APPLICATION_JSON}) @Consumes({MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_JSON}) @Produces({MediaType.APPLICATION_JSON})
public Response modifyLoanApplication( public String modifyLoanApplication(
@PathParam("loanId") final Long loanId, @PathParam("loanId") final Long loanId,
@QueryParam("command") final String commandParam, // @QueryParam("command") final String commandParam,
// @Context final UriInfo uriInfo,
final String jsonRequestBody) { final String jsonRequestBody) {
final LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(loanId, jsonRequestBody); final LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(loanId, jsonRequestBody);
// for now just auto generating the loan schedule and setting support // if (is(commandParam, "calculateLoanSchedule")) {
// for 'manual' loan schedule creation later. // CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
// command.setLoanSchedule(loanSchedule); // return calculateLoanSchedule(uriInfo, calculateLoanScheduleCommand);
// }
if (is(commandParam, "calculateLoanSchedule")) { final EntityIdentifier identifier = this.loanWritePlatformService.modifyLoanApplication(command);
final CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
final LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateLoanScheduleCommand);
return Response.ok().entity(loanSchedule).build();
}
EntityIdentifier identifier = this.loanWritePlatformService.modifyLoanApplication(command); return this.apiJsonSerializerService.serializeEntityIdentifier(identifier);
return Response.ok().entity(identifier).build();
} }
@DELETE @DELETE
@@ -324,8 +324,7 @@ public class LoansApiResource {
@Produces({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON })
public Response deleteLoanApplication(@PathParam("loanId") final Long loanId) { public Response deleteLoanApplication(@PathParam("loanId") final Long loanId) {
EntityIdentifier identifier = this.loanWritePlatformService EntityIdentifier identifier = this.loanWritePlatformService.deleteLoan(loanId);
.deleteLoan(loanId);
return Response.ok().entity(identifier).build(); return Response.ok().entity(identifier).build();
} }
@@ -3,6 +3,7 @@ package org.mifosng.platform.api;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedulePeriodData; import org.mifosng.platform.api.data.LoanSchedulePeriodData;
/** /**
@@ -16,10 +17,18 @@ import org.mifosng.platform.api.data.LoanSchedulePeriodData;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class NewLoanScheduleData { public class NewLoanScheduleData {
private final Integer loanTermInDays;
/**
* The currency associated with all monetary values in loan schedule.
*/
private final CurrencyData currency;
/** /**
* The cumulative total of all money (principal) disbursed to the loan applicant. * The cumulative total of all money (principal) disbursed to the loan applicant.
*/ */
private final BigDecimal cumulativePrincipalDisbursed; private final BigDecimal cumulativePrincipalDisbursed;
private final BigDecimal cumulativePrincipalDue;
private final BigDecimal cumulativePrincipalPaid; private final BigDecimal cumulativePrincipalPaid;
private final BigDecimal cumulativePrincipalOutstanding; private final BigDecimal cumulativePrincipalOutstanding;
@@ -55,13 +64,16 @@ public class NewLoanScheduleData {
private final BigDecimal totalInArrears; private final BigDecimal totalInArrears;
/** /**
* <code>periods</code> * <code>periods</code> is collection of data objects containing specific
* information to each period of the loan schedule including disbursment and
* repayment information.
*/ */
private final Collection<LoanSchedulePeriodData> periods; private final Collection<LoanSchedulePeriodData> periods;
public NewLoanScheduleData() { public NewLoanScheduleData() {
this.cumulativePrincipalDisbursed = null; this.cumulativePrincipalDisbursed = null;
this.cumulativePrincipalPaid = null; this.cumulativePrincipalPaid = null;
this.cumulativePrincipalDue = null;
this.cumulativePrincipalOutstanding = null; this.cumulativePrincipalOutstanding = null;
this.cumulativeInterestExpected = null; this.cumulativeInterestExpected = null;
@@ -78,28 +90,52 @@ public class NewLoanScheduleData {
this.totalExpectedRepayment = null; this.totalExpectedRepayment = null;
this.totalInArrears = null; this.totalInArrears = null;
this.currency = null;
this.periods = null; this.periods = null;
this.loanTermInDays = null;
} }
public NewLoanScheduleData(final Collection<LoanSchedulePeriodData> periods) { public NewLoanScheduleData(
final CurrencyData currency,
final Collection<LoanSchedulePeriodData> periods,
final Integer loanTermInDays,
final BigDecimal cumulativePrincipalDisbursed,
final BigDecimal cumulativePrincipalDue,
final BigDecimal cumulativePrincipalOutstanding,
final BigDecimal cumulativeInterestExpected,
final BigDecimal cumulativeChargesToDate,
final BigDecimal totalExpectedRepayment) {
this.currency = currency;
this.periods = periods; this.periods = periods;
this.loanTermInDays = loanTermInDays;
this.cumulativePrincipalDisbursed = null; this.cumulativePrincipalDisbursed = cumulativePrincipalDisbursed;
this.cumulativePrincipalDue = cumulativePrincipalDue;
this.cumulativePrincipalPaid = null; this.cumulativePrincipalPaid = null;
this.cumulativePrincipalOutstanding = null; this.cumulativePrincipalOutstanding = cumulativePrincipalOutstanding;
this.cumulativeInterestExpected = null; this.cumulativeInterestExpected = cumulativeInterestExpected;
this.cumulativeInterestPaid = null; this.cumulativeInterestPaid = null;
this.cumulativeInterestWaived = null; this.cumulativeInterestWaived = null;
this.cumulativeInterestOutstanding = null; this.cumulativeInterestOutstanding = null;
this.cumulativeChargesToDate = null; this.cumulativeChargesToDate = cumulativeChargesToDate;
this.cumulativeChargesPaid = null; this.cumulativeChargesPaid = null;
this.cumulativeChargesOutstanding = null; this.cumulativeChargesOutstanding = null;
this.totalCostOfLoan = null; if (cumulativeChargesToDate != null) {
this.totalCostOfLoan = cumulativeInterestExpected.add(cumulativeChargesToDate);
} else if (cumulativeInterestExpected != null) {
this.totalCostOfLoan = cumulativeInterestExpected;
} else {
this.totalCostOfLoan = null;
}
this.totalExpectedRepayment = null; this.totalExpectedRepayment = totalExpectedRepayment;
this.totalInArrears = null; this.totalInArrears = null;
} }
public Collection<LoanSchedulePeriodData> getPeriods() {
return periods;
}
} }
@@ -3,6 +3,7 @@ package org.mifosng.platform.api.data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Deprecated
public class LoanSchedule { public class LoanSchedule {
private List<ScheduledLoanInstallment> scheduledLoanInstallments = new ArrayList<ScheduledLoanInstallment>(); private List<ScheduledLoanInstallment> scheduledLoanInstallments = new ArrayList<ScheduledLoanInstallment>();
@@ -111,4 +111,28 @@ public class LoanSchedulePeriodData {
this.totalDueForPeriod = totalDueForPeriod; this.totalDueForPeriod = totalDueForPeriod;
} }
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;
}
public LocalDate periodDueDate() {
return this.dueDate;
}
public BigDecimal principalDue() {
return this.principalDue;
}
public BigDecimal interestDue() {
return this.interestDue;
}
} }
@@ -4,6 +4,7 @@ import java.math.BigDecimal;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
@Deprecated
public class ScheduledLoanInstallment { public class ScheduledLoanInstallment {
private Integer installmentNumber; private Integer installmentNumber;
@@ -439,56 +439,54 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
throw new InvalidJsonException(); throw new InvalidJsonException();
} }
Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType(); Type typeOfMap = new TypeToken<Map<String, Object>>(){}.getType();
Map<String, String> requestMap = gsonConverter.fromJson(json, typeOfMap); Map<String, Object> requestMap = gsonConverter.fromJson(json, typeOfMap);
Set<String> supportedParams = new HashSet<String>( Set<String> supportedParams = new HashSet<String>(
Arrays.asList("clientId", "productId", "externalId", "fundId", "transactionProcessingStrategyId", Arrays.asList("clientId", "productId", "externalId", "fundId", "transactionProcessingStrategyId",
"principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments",
"loanTermFrequency", "loanTermFrequencyType", "charges", "loanTermFrequency", "loanTermFrequencyType", "charges",
"repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "repaymentFrequencyType", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType",
"expectedDisbursementDate", "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "expectedDisbursementDate", "repaymentsStartingFromDate", "interestChargedFromDate", "submittedOnDate", "submittedOnNote",
"locale", "dateFormat", "loanOfficerId") "locale", "dateFormat", "loanOfficerId", "id")
); );
checkForUnsupportedParameters(requestMap, supportedParams); checkForUnsupportedParameters(requestMap, supportedParams);
Set<String> modifiedParameters = new HashSet<String>(); Set<String> modifiedParameters = new HashSet<String>();
Long clientId = extractLongParameter("clientId", requestMap, modifiedParameters);
Long productId = extractLongParameter("productId", requestMap, modifiedParameters);
Long fundId = extractLongParameter("fundId", requestMap, modifiedParameters);
Long loanOfficerId = extractLongParameter("loanOfficerId", requestMap, modifiedParameters);
Long transactionProcessingStrategyId = extractLongParameter("transactionProcessingStrategyId", requestMap, modifiedParameters);
String externalId = extractStringParameter("externalId", requestMap, modifiedParameters);
BigDecimal principal = extractBigDecimalParameter("principal", requestMap, modifiedParameters); JsonParser parser = new JsonParser();
BigDecimal inArrearsToleranceValue = extractBigDecimalParameter("inArrearsTolerance", requestMap, modifiedParameters); JsonElement element = parser.parse(json);
BigDecimal interestRatePerPeriod = extractBigDecimalParameter("interestRatePerPeriod", requestMap, modifiedParameters); JsonParserHelper helper = new JsonParserHelper();
Integer repaymentEvery = extractIntegerParameter("repaymentEvery", requestMap, modifiedParameters); final Long clientId = helper.extractLongNamed("clientId", element, modifiedParameters);
Integer numberOfRepayments = extractIntegerParameter("numberOfRepayments", requestMap, modifiedParameters); final Long productId = helper.extractLongNamed("productId", element, modifiedParameters);
Integer repaymentFrequencyType = extractIntegerParameter("repaymentFrequencyType", requestMap, modifiedParameters); final Long fundId = helper.extractLongNamed("fundId", element, modifiedParameters);
final Long loanOfficerId = helper.extractLongNamed("loanOfficerId", element, modifiedParameters);
final Long transactionProcessingStrategyId = helper.extractLongNamed("transactionProcessingStrategyId", element, modifiedParameters);
final String externalId = helper.extractStringNamed("externalId", element, modifiedParameters);
final BigDecimal principal = helper.extractBigDecimalNamed("principal", element, modifiedParameters);
final BigDecimal inArrearsToleranceValue = helper.extractBigDecimalNamed("inArrearsTolerance", element, modifiedParameters);
final BigDecimal interestRatePerPeriod = helper.extractBigDecimalNamed("interestRatePerPeriod", element, modifiedParameters);
Integer loanTermFrequency = extractIntegerParameter("loanTermFrequency", requestMap, modifiedParameters); final Integer repaymentEvery = helper.extractIntegerNamed("repaymentEvery", element, modifiedParameters);
Integer loanTermFrequencyType = extractIntegerParameter("loanTermFrequencyType", requestMap, modifiedParameters); final Integer numberOfRepayments = helper.extractIntegerNamed("numberOfRepayments", element, modifiedParameters);
final Integer repaymentFrequencyType = helper.extractIntegerNamed("repaymentFrequencyType", element, modifiedParameters);
final Integer loanTermFrequency = helper.extractIntegerNamed("loanTermFrequency", element, modifiedParameters);
final Integer loanTermFrequencyType = helper.extractIntegerNamed("loanTermFrequencyType", element, modifiedParameters);
final Integer interestRateFrequencyType = helper.extractIntegerNamed("interestRateFrequencyType", element, modifiedParameters);
final Integer amortizationType = helper.extractIntegerNamed("amortizationType", element, modifiedParameters);
final Integer interestType = helper.extractIntegerNamed("interestType", element, modifiedParameters);
final Integer interestCalculationPeriodType = helper.extractIntegerNamed("interestCalculationPeriodType", element, modifiedParameters);
Integer interestRateFrequencyTypeValue = extractIntegerParameter("interestRateFrequencyType", requestMap, modifiedParameters); final LocalDate expectedDisbursementDate = helper.extractLocalDateNamed("expectedDisbursementDate", element, modifiedParameters);
Integer amortizationTypeValue = extractIntegerParameter("amortizationType", requestMap, modifiedParameters); final LocalDate repaymentsStartingFromDate = helper.extractLocalDateNamed("repaymentsStartingFromDate", element, modifiedParameters);
Integer interestTypeValue = extractIntegerParameter("interestType", requestMap, modifiedParameters); final LocalDate interestChargedFromDate = helper.extractLocalDateNamed("interestChargedFromDate", element, modifiedParameters);
Integer interestCalculationPeriodTypeValue = extractIntegerParameter("interestCalculationPeriodType", requestMap, modifiedParameters); final LocalDate submittedOnDate = helper.extractLocalDateNamed("submittedOnDate", element, modifiedParameters);
LocalDate expectedDisbursementDate = extractLocalDateParameter("expectedDisbursementDate", requestMap, modifiedParameters); final String submittedOnNote = helper.extractStringNamed("submittedOnNote", element, modifiedParameters);
LocalDate repaymentsStartingFromDate = extractLocalDateParameter("repaymentsStartingFromDate", requestMap, modifiedParameters);
LocalDate interestChargedFromDate = extractLocalDateParameter("interestChargedFromDate", requestMap, modifiedParameters);
LocalDate submittedOnDate = extractLocalDateParameter("submittedOnDate", requestMap, modifiedParameters);
String submittedOnNote = extractStringParameter("submittedOnNote", requestMap, modifiedParameters);
JsonParser parser = new JsonParser();
LoanChargeCommand[] charges = null; LoanChargeCommand[] charges = null;
JsonElement element = parser.parse(json);
if (element.isJsonObject()) { if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject(); JsonObject object = element.getAsJsonObject();
if (object.has("charges") && object.get("charges").isJsonArray()) { if (object.has("charges") && object.get("charges").isJsonArray()) {
@@ -518,8 +516,8 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
resourceIdentifier, clientId, productId, externalId, fundId, transactionProcessingStrategyId, resourceIdentifier, clientId, productId, externalId, fundId, transactionProcessingStrategyId,
submittedOnDate, submittedOnNote, submittedOnDate, submittedOnNote,
expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate,
principal, interestRatePerPeriod, interestRateFrequencyTypeValue, interestTypeValue, interestCalculationPeriodTypeValue, principal, interestRatePerPeriod, interestRateFrequencyType, interestType, interestCalculationPeriodType,
repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationTypeValue, repaymentEvery, repaymentFrequencyType, numberOfRepayments, amortizationType,
loanTermFrequency, loanTermFrequencyType, loanTermFrequency, loanTermFrequencyType,
inArrearsToleranceValue, charges,loanOfficerId); inArrearsToleranceValue, charges,loanOfficerId);
} }
@@ -0,0 +1,353 @@
package org.mifosng.platform.api.infrastructure;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate;
import org.joda.time.format.DateTimeFormat;
import org.mifosng.platform.api.data.ApiParameterError;
import org.mifosng.platform.exceptions.PlatformApiDataValidationException;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.format.number.NumberFormatter;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* Helper class to extract values of json named attributes.
*/
public class JsonParserHelper {
public Long extractLongNamed(final String parameterName, final JsonElement element, final Set<String> modifiedParameters) {
Long longValue = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
modifiedParameters.add(parameterName);
JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
final String stringValue = primitive.getAsString();
if (StringUtils.isNotBlank(stringValue)) {
longValue = Long.valueOf(stringValue);
}
}
}
return longValue;
}
public String extractStringNamed(final String parameterName, final JsonElement element, final Set<String> modifiedParameters) {
String stringValue = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
modifiedParameters.add(parameterName);
JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
if (StringUtils.isNotBlank(stringValue)) {
stringValue = primitive.getAsString();
}
}
}
return stringValue;
}
public BigDecimal extractBigDecimalNamed(final String parameterName, final JsonElement element, final Set<String> modifiedParameters) {
BigDecimal value = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
modifiedParameters.add(parameterName);
JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) {
value = convertFrom(valueAsString, parameterName, extractLocaleValue(object));
}
}
}
return value;
}
public Integer extractIntegerNamed(final String parameterName, final JsonElement element, final Set<String> modifiedParameters) {
Integer value = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
modifiedParameters.add(parameterName);
JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) {
value = convertToInteger(valueAsString, parameterName, extractLocaleValue(object));
}
}
}
return value;
}
public LocalDate extractLocalDateNamed(final String parameterName, final JsonElement element, final Set<String> modifiedParameters) {
LocalDate value = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
String dateFormat = null;
final String dateFormatParameter = "dateFormat";
if (object.has(dateFormatParameter) && object.get(dateFormatParameter).isJsonPrimitive()) {
JsonPrimitive primitive = object.get(dateFormatParameter).getAsJsonPrimitive();
dateFormat = primitive.getAsString();
}
if (object.has(parameterName) && object.get(parameterName).isJsonPrimitive()) {
modifiedParameters.add(parameterName);
JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
final String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) {
value = convertFrom(valueAsString, parameterName, dateFormat);
}
}
}
return value;
}
private LocalDate convertFrom(final String dateAsString, final String parameterName, final String dateFormat) {
if (StringUtils.isBlank(dateFormat)) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
String defaultMessage = new StringBuilder("The parameter '" + parameterName + "' requires a 'dateFormat' parameter to be passed with it.").toString();
ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.dateFormat.parameter", defaultMessage, parameterName);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
LocalDate eventLocalDate = null;
if (StringUtils.isNotBlank(dateAsString)) {
try {
Locale locale = LocaleContextHolder.getLocale();
eventLocalDate = DateTimeFormat.forPattern(dateFormat)
.withLocale(locale)
.parseLocalDate(dateAsString.toLowerCase(locale));
} catch (IllegalArgumentException e) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError
.parameterError(
"validation.msg.invalid.date.format",
"The parameter "
+ parameterName
+ " is invalid based on the dateFormat provided:"
+ dateFormat, parameterName,
dateAsString, dateFormat);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
}
return eventLocalDate;
}
private BigDecimal convertFrom(final String numericalValueFormatted, final String parameterName, final Locale clientApplicationLocale) {
if (clientApplicationLocale == null) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
String defaultMessage = new StringBuilder("The parameter '" + parameterName + "' requires a 'locale' parameter to be passed with it.").toString();
ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage, parameterName);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
try {
BigDecimal number = null;
if (StringUtils.isNotBlank(numericalValueFormatted)) {
String source = numericalValueFormatted.trim();
NumberFormat format = NumberFormat.getNumberInstance(clientApplicationLocale);
DecimalFormat df = (DecimalFormat) format;
DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
// http://bugs.sun.com/view_bug.do?bug_id=4510618
char groupingSeparator = symbols.getGroupingSeparator();
if (groupingSeparator == '\u00a0') {
source = source.replaceAll(" ", Character.toString('\u00a0'));
}
NumberFormatter numberFormatter = new NumberFormatter();
Number parsedNumber = numberFormatter.parse(source, clientApplicationLocale);
number = BigDecimal.valueOf(Double.valueOf(parsedNumber.doubleValue()));
}
return number;
} catch (ParseException e) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError.parameterError(
"validation.msg.invalid.decimal.format", "The parameter "
+ parameterName + " has value: " + numericalValueFormatted + " which is invalid decimal value for provided locale of ["
+ clientApplicationLocale.toString() + "].",
parameterName, numericalValueFormatted,
clientApplicationLocale);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
}
private Integer convertToInteger(final String numericalValueFormatted, final String parameterName, final Locale clientApplicationLocale) {
if (clientApplicationLocale == null) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
String defaultMessage = new StringBuilder("The parameter '" + parameterName + "' requires a 'locale' parameter to be passed with it.").toString();
ApiParameterError error = ApiParameterError.parameterError("validation.msg.missing.locale.parameter", defaultMessage, parameterName);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
try {
Integer number = null;
if (StringUtils.isNotBlank(numericalValueFormatted)) {
String source = numericalValueFormatted.trim();
NumberFormat format = NumberFormat.getInstance(clientApplicationLocale);
DecimalFormat df = (DecimalFormat) format;
DecimalFormatSymbols symbols = df.getDecimalFormatSymbols();
df.setParseBigDecimal(true);
// http://bugs.sun.com/view_bug.do?bug_id=4510618
char groupingSeparator = symbols.getGroupingSeparator();
if (groupingSeparator == '\u00a0') {
source = source.replaceAll(" ", Character.toString('\u00a0'));
}
Number parsedNumber = df.parse(source);
double parsedNumberDouble = parsedNumber.doubleValue();
int parsedNumberInteger = parsedNumber.intValue();
if (source.contains(Character.toString(symbols.getDecimalSeparator()))) {
throw new ParseException(source, 0);
}
if (!Double.valueOf(parsedNumberDouble).equals(Double.valueOf(Integer.valueOf(parsedNumberInteger)))) {
throw new ParseException(source, 0);
}
number = parsedNumber.intValue();
}
return number;
} catch (ParseException e) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError.parameterError(
"validation.msg.invalid.integer.format", "The parameter "
+ parameterName + " has value: " + numericalValueFormatted + " which is invalid integer value for provided locale of ["
+ clientApplicationLocale.toString() + "].",
parameterName, numericalValueFormatted,
clientApplicationLocale);
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
}
private Locale localeFromString(final String localeAsString) {
if (StringUtils.isBlank(localeAsString)) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format", "The parameter locale is invalid. It cannot be blank.", "locale");
dataValidationErrors.add(error);
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
String languageCode = "";
String courntryCode = "";
String variantCode = "";
String[] localeParts = localeAsString.split("_");
if (localeParts != null && localeParts.length == 1) {
languageCode = localeParts[0];
}
if (localeParts != null && localeParts.length == 2) {
languageCode = localeParts[0];
courntryCode = localeParts[1];
}
if (localeParts != null && localeParts.length == 3) {
languageCode = localeParts[0];
courntryCode = localeParts[1];
variantCode = localeParts[2];
}
return localeFrom(languageCode, courntryCode, variantCode);
}
private Locale localeFrom(final String languageCode, final String courntryCode, final String variantCode) {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
List<String> allowedLanguages = Arrays.asList(Locale.getISOLanguages());
if (!allowedLanguages.contains(languageCode.toLowerCase())) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format", "The parameter locale has an invalid language value " + languageCode + " .", "locale", languageCode);
dataValidationErrors.add(error);
}
if (StringUtils.isNotBlank(courntryCode.toUpperCase())) {
List<String> allowedCountries = Arrays.asList(Locale.getISOCountries());
if (!allowedCountries.contains(courntryCode)) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.locale.format", "The parameter locale has an invalid country value " + courntryCode + " .", "locale", courntryCode);
dataValidationErrors.add(error);
}
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
return new Locale(languageCode.toLowerCase(), courntryCode.toUpperCase(), variantCode);
}
private Locale extractLocaleValue(final JsonObject object) {
Locale clientApplicationLocale = null;
String locale = null;
if (object.has("locale") && object.get("locale").isJsonPrimitive()) {
JsonPrimitive primitive = object.get("locale").getAsJsonPrimitive();
locale = primitive.getAsString();
clientApplicationLocale = localeFromString(locale);
}
return clientApplicationLocale;
}
}
@@ -27,10 +27,9 @@ import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption; import org.hibernate.annotations.LazyCollectionOption;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.commands.LoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.client.domain.Client; import org.mifosng.platform.client.domain.Client;
import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
@@ -244,7 +243,7 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
} }
public void modifyLoanApplication(final LoanApplicationCommand command, final Client client, final LoanProduct loanProduct, public void modifyLoanApplication(final LoanApplicationCommand command, final Client client, final LoanProduct loanProduct,
final Fund fund, final LoanTransactionProcessingStrategy strategy, final LoanSchedule modifiedLoanSchedule, final Set<LoanCharge> charges, final Fund fund, final LoanTransactionProcessingStrategy strategy, final NewLoanScheduleData modifiedLoanSchedule, final Set<LoanCharge> charges,
final Staff loanOfficer) { final Staff loanOfficer) {
if (command.isClientChanged()) { if (command.isClientChanged()) {
@@ -310,16 +309,16 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
// FIXME - rewrite over loan schedule by default for now but worth putting in check to see if required // FIXME - rewrite over loan schedule by default for now but worth putting in check to see if required
// i.e. only a client change wouldn't require it, only if one of parameters related to loan schedule calculation is changed. // i.e. only a client change wouldn't require it, only if one of parameters related to loan schedule calculation is changed.
this.repaymentScheduleInstallments.clear(); this.repaymentScheduleInstallments.clear();
for (ScheduledLoanInstallment scheduledLoanInstallment : modifiedLoanSchedule.getScheduledLoanInstallments()) { for (LoanSchedulePeriodData scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
MoneyData readPrincipalDue = scheduledLoanInstallment.getPrincipalDue(); if (scheduledLoanInstallment.isRepaymentPeriod()) {
MoneyData readInterestDue = scheduledLoanInstallment.getInterestDue(); LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
this, scheduledLoanInstallment.periodNumber(),
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment( scheduledLoanInstallment.periodDueDate(),
this, scheduledLoanInstallment.getInstallmentNumber(), scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.getPeriodEnd(), readPrincipalDue.getAmount(), scheduledLoanInstallment.interestDue());
readInterestDue.getAmount()); this.addRepaymentScheduleInstallment(installment);
this.addRepaymentScheduleInstallment(installment); }
} }
// if the loan application/contract is modified when repayments are already against it - then need to re-process it // if the loan application/contract is modified when repayments are already against it - then need to re-process it
@@ -3,12 +3,9 @@ package org.mifosng.platform.loan.service;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData; import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand; import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.data.LoanSchedule;
public interface CalculationPlatformService { public interface CalculationPlatformService {
LoanSchedule calculateLoanSchedule(CalculateLoanScheduleCommand command);
NewLoanScheduleData calculateLoanScheduleNew(CalculateLoanScheduleCommand command); NewLoanScheduleData calculateLoanScheduleNew(CalculateLoanScheduleCommand command);
/** /**
@@ -1,30 +1,20 @@
package org.mifosng.platform.loan.service; package org.mifosng.platform.loan.service;
import java.math.BigDecimal;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData; import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand; import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.data.CurrencyData; import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.currency.domain.ApplicationCurrency; import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.currency.domain.ApplicationCurrencyRepository; import org.mifosng.platform.currency.domain.ApplicationCurrencyRepository;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.exceptions.CurrencyNotFoundException; import org.mifosng.platform.exceptions.CurrencyNotFoundException;
import org.mifosng.platform.exceptions.LoanNotFoundException; import org.mifosng.platform.exceptions.LoanNotFoundException;
import org.mifosng.platform.exceptions.LoanProductNotFoundException;
import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.InterestCalculationPeriodMethod;
import org.mifosng.platform.loan.domain.InterestMethod; import org.mifosng.platform.loan.domain.InterestMethod;
import org.mifosng.platform.loan.domain.Loan; import org.mifosng.platform.loan.domain.Loan;
import org.mifosng.platform.loan.domain.LoanPayoffSummary; import org.mifosng.platform.loan.domain.LoanPayoffSummary;
import org.mifosng.platform.loan.domain.LoanProduct;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.LoanProductRepository;
import org.mifosng.platform.loan.domain.LoanRepository; import org.mifosng.platform.loan.domain.LoanRepository;
import org.mifosng.platform.loan.domain.PeriodFrequencyType; import org.mifosng.platform.loan.domain.PeriodFrequencyType;
import org.mifosng.platform.loanschedule.domain.AprCalculator;
import org.mifosng.platform.loanschedule.domain.DefaultLoanScheduleGeneratorFactory; import org.mifosng.platform.loanschedule.domain.DefaultLoanScheduleGeneratorFactory;
import org.mifosng.platform.loanschedule.domain.LoanScheduleGenerator; import org.mifosng.platform.loanschedule.domain.LoanScheduleGenerator;
import org.mifosng.platform.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosng.platform.loanschedule.domain.LoanScheduleGeneratorFactory;
@@ -37,9 +27,7 @@ public class CalculationPlatformServiceImpl implements CalculationPlatformServic
private final LoanScheduleGeneratorFactory loanScheduleFactory; private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final LoanProductRepository loanProductRepository;
private final ApplicationCurrencyRepository applicationCurrencyRepository; private final ApplicationCurrencyRepository applicationCurrencyRepository;
private final AprCalculator aprCalculator;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final LoanProductRelatedDetailAssembler loanProductRelatedDetailAssembler; private final LoanProductRelatedDetailAssembler loanProductRelatedDetailAssembler;
@@ -47,15 +35,11 @@ public class CalculationPlatformServiceImpl implements CalculationPlatformServic
public CalculationPlatformServiceImpl( public CalculationPlatformServiceImpl(
final PlatformSecurityContext context, final PlatformSecurityContext context,
final LoanRepository loanRepository, final LoanRepository loanRepository,
final LoanProductRepository loanProductRepository,
final ApplicationCurrencyRepository applicationCurrencyRepository, final ApplicationCurrencyRepository applicationCurrencyRepository,
final AprCalculator aprCalculator,
final LoanProductRelatedDetailAssembler loanProductRelatedDetailAssembler) { final LoanProductRelatedDetailAssembler loanProductRelatedDetailAssembler) {
this.context = context; this.context = context;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanProductRepository = loanProductRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository; this.applicationCurrencyRepository = applicationCurrencyRepository;
this.aprCalculator = aprCalculator;
this.loanScheduleFactory = new DefaultLoanScheduleGeneratorFactory(); this.loanScheduleFactory = new DefaultLoanScheduleGeneratorFactory();
this.loanProductRelatedDetailAssembler = loanProductRelatedDetailAssembler; this.loanProductRelatedDetailAssembler = loanProductRelatedDetailAssembler;
} }
@@ -70,76 +54,19 @@ public class CalculationPlatformServiceImpl implements CalculationPlatformServic
LoanProductRelatedDetail loanScheduleRelatedDetails = this.loanProductRelatedDetailAssembler.assembleFrom(command); LoanProductRelatedDetail loanScheduleRelatedDetails = this.loanProductRelatedDetailAssembler.assembleFrom(command);
// final Integer loanTermFrequency = command.getLoanTermFrequency();
// final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod);
// ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(loanScheduleRelatedDetails.getCurrency().getCode());
// CurrencyData currencyData = new CurrencyData(
// applicationCurrency.getCode(),
// applicationCurrency.getName(),
// loanScheduleRelatedDetails.getCurrency().getDigitsAfterDecimal(),
// applicationCurrency.getDisplaySymbol(),
// applicationCurrency.getNameCode());
return loanScheduleGenerator.generate(loanScheduleRelatedDetails,
command.getExpectedDisbursementDate(),
command.getRepaymentsStartingFromDate(),
command.getInterestChargedFromDate());
}
@Override
public LoanSchedule calculateLoanSchedule(final CalculateLoanScheduleCommand command) {
context.authenticatedUser();
CalculateLoanScheduleCommandValidator validator = new CalculateLoanScheduleCommandValidator(command);
validator.validate();
final BigDecimal principalAmount = command.getPrincipal();
final BigDecimal defaultNominalInterestRatePerPeriod = command.getInterestRatePerPeriod();
final PeriodFrequencyType interestPeriodFrequencyType = PeriodFrequencyType.fromInt(command.getInterestRateFrequencyType());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod.fromInt(command.getInterestCalculationPeriodType());
final Integer loanTermFrequency = command.getLoanTermFrequency(); final Integer loanTermFrequency = command.getLoanTermFrequency();
final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType()); final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType());
final InterestMethod interestMethod = InterestMethod.fromInt(command.getInterestType());
final Integer defaultNumberOfInstallments = command.getNumberOfRepayments();
final Integer repayEvery = command.getRepaymentEvery();
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command.getRepaymentFrequencyType());
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.getAmortizationType());
final BigDecimal defaultAnnualNominalInterestRate = this.aprCalculator.calculateFrom(interestPeriodFrequencyType, defaultNominalInterestRatePerPeriod);
LoanProduct loanProduct = this.loanProductRepository.findOne(command.getProductId());
if (loanProduct == null) {
throw new LoanProductNotFoundException(command.getProductId());
}
final MonetaryCurrency currency = loanProduct.getCurrency();
// in arrerars tolerance isnt relevant when auto-calculating a loan schedule
final BigDecimal inArrearsTolerance = BigDecimal.ZERO;
LoanProductRelatedDetail loanScheduleInfo = new LoanProductRelatedDetail(currency, principalAmount,
defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate,
interestMethod, interestCalculationPeriodMethod,
repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, amortizationMethod,
inArrearsTolerance);
LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod); LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(interestMethod);
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(currency.getCode()); ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneByCode(loanScheduleRelatedDetails.getCurrency().getCode());
CurrencyData currencyData = new CurrencyData(applicationCurrency.getCode(), applicationCurrency.getName(), currency.getDigitsAfterDecimal(),
applicationCurrency.getDisplaySymbol(), applicationCurrency.getNameCode());
return loanScheduleGenerator.generate(loanScheduleInfo, loanTermFrequency, loanTermFrequencyType, command.getExpectedDisbursementDate(), command.getRepaymentsStartingFromDate(), return loanScheduleGenerator.generate(applicationCurrency,
command.getInterestChargedFromDate(), currencyData); loanScheduleRelatedDetails, loanTermFrequency,
loanTermFrequencyType, command.getExpectedDisbursementDate(),
command.getRepaymentsStartingFromDate(),
command.getInterestChargedFromDate());
} }
@Override @Override
@@ -1,16 +1,15 @@
package org.mifosng.platform.loan.service; package org.mifosng.platform.loan.service;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.commands.LoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
import org.mifosng.platform.api.commands.LoanChargeCommand; import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.charge.domain.Charge; import org.mifosng.platform.charge.domain.Charge;
import org.mifosng.platform.charge.domain.ChargeRepository; import org.mifosng.platform.charge.domain.ChargeRepository;
import org.mifosng.platform.client.domain.Client; import org.mifosng.platform.client.domain.Client;
@@ -59,6 +58,7 @@ public class LoanAssembler {
private final ChargeRepository chargeRepository; private final ChargeRepository chargeRepository;
private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository; private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository;
private final StaffRepository staffRepository; private final StaffRepository staffRepository;
private final CalculationPlatformService calculationPlatformService;
@Autowired @Autowired
public LoanAssembler( public LoanAssembler(
@@ -67,13 +67,15 @@ public class LoanAssembler {
final FundRepository fundRepository, final FundRepository fundRepository,
final ChargeRepository chargeRepository, final ChargeRepository chargeRepository,
final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository, final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
final StaffRepository staffRepository) { final StaffRepository staffRepository,
final CalculationPlatformService calculationPlatformService) {
this.loanProductRepository = loanProductRepository; this.loanProductRepository = loanProductRepository;
this.clientRepository = clientRepository; this.clientRepository = clientRepository;
this.fundRepository = fundRepository; this.fundRepository = fundRepository;
this.chargeRepository = chargeRepository; this.chargeRepository = chargeRepository;
this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository; this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository;
this.staffRepository = staffRepository; this.staffRepository = staffRepository;
this.calculationPlatformService = calculationPlatformService;
} }
public Loan assembleFrom(final LoanApplicationCommand command) { public Loan assembleFrom(final LoanApplicationCommand command) {
@@ -107,21 +109,17 @@ public class LoanAssembler {
Loan loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges); Loan loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges);
loan.setExternalId(command.getExternalId()); loan.setExternalId(command.getExternalId());
// TODO - user service to calculate loan schedule at this point final NewLoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
// LoanSchedule loanSchedule = command.getLoanSchedule();
List<ScheduledLoanInstallment> loanRepaymentSchedule = new ArrayList<ScheduledLoanInstallment>();
// loanSchedule.getScheduledLoanInstallments();
for (ScheduledLoanInstallment scheduledLoanInstallment : loanRepaymentSchedule) { for (LoanSchedulePeriodData scheduledLoanInstallment : loanSchedule.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) {
MoneyData readPrincipalDue = scheduledLoanInstallment.getPrincipalDue(); LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
MoneyData readInterestDue = scheduledLoanInstallment.getInterestDue(); loan, scheduledLoanInstallment.periodNumber(),
scheduledLoanInstallment.periodDueDate(),
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment( scheduledLoanInstallment.principalDue(),
loan, scheduledLoanInstallment.getInstallmentNumber(), scheduledLoanInstallment.interestDue());
scheduledLoanInstallment.getPeriodEnd(), readPrincipalDue.getAmount(), loan.addRepaymentScheduleInstallment(installment);
readInterestDue.getAmount()); }
loan.addRepaymentScheduleInstallment(installment);
} }
loan.submitApplication(loanTermFrequency, loanTermFrequencyType, loan.submitApplication(loanTermFrequency, loanTermFrequencyType,
@@ -8,6 +8,7 @@ import java.util.Set;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand; import org.mifosng.platform.api.commands.AdjustLoanTransactionCommand;
import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand; import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
import org.mifosng.platform.api.commands.LoanApplicationCommand; import org.mifosng.platform.api.commands.LoanApplicationCommand;
@@ -15,13 +16,11 @@ import org.mifosng.platform.api.commands.LoanStateTransitionCommand;
import org.mifosng.platform.api.commands.LoanTransactionCommand; import org.mifosng.platform.api.commands.LoanTransactionCommand;
import org.mifosng.platform.api.commands.UndoStateTransitionCommand; import org.mifosng.platform.api.commands.UndoStateTransitionCommand;
import org.mifosng.platform.api.data.EntityIdentifier; import org.mifosng.platform.api.data.EntityIdentifier;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.client.domain.Client; import org.mifosng.platform.client.domain.Client;
import org.mifosng.platform.client.domain.ClientRepository; import org.mifosng.platform.client.domain.ClientRepository;
import org.mifosng.platform.client.domain.Note; import org.mifosng.platform.client.domain.Note;
import org.mifosng.platform.client.domain.NoteRepository; import org.mifosng.platform.client.domain.NoteRepository;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.exceptions.ClientNotFoundException; import org.mifosng.platform.exceptions.ClientNotFoundException;
import org.mifosng.platform.exceptions.LoanNotFoundException; import org.mifosng.platform.exceptions.LoanNotFoundException;
@@ -147,7 +146,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId()); LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
Set<LoanCharge> charges = this.loanAssembler.assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges(), loan.getCurrency().getCode()); Set<LoanCharge> charges = this.loanAssembler.assembleSetOfLoanCharges(command.getCharges(), loanProduct.getCharges(), loan.getCurrency().getCode());
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand()); final NewLoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, loanOfficer); loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, loanOfficer);
this.loanRepository.save(loan); this.loanRepository.save(loan);
@@ -332,7 +331,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Integer selectedRepaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue(); Integer selectedRepaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType().getValue();
Integer selectedAmortizationMethod = loan.repaymentScheduleDetail().getAmortizationMethod().getValue(); Integer selectedAmortizationMethod = loan.repaymentScheduleDetail().getAmortizationMethod().getValue();
// FIXME - use values from loan table here instead of inferring. // FIXME - KW - use values from loan table here instead of inferring.
Integer loanTermFrequency = repaidEvery * numberOfInstallments; Integer loanTermFrequency = repaidEvery * numberOfInstallments;
Integer loanTermFrequencyType = selectedRepaymentFrequency; Integer loanTermFrequencyType = selectedRepaymentFrequency;
@@ -351,29 +350,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanTermFrequency, loanTermFrequencyType, loanTermFrequency, loanTermFrequencyType,
actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate); actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate);
LoanSchedule loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateCommand); NewLoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(calculateCommand);
List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule = new ArrayList<LoanRepaymentScheduleInstallment>(); List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule = new ArrayList<LoanRepaymentScheduleInstallment>();
for (ScheduledLoanInstallment scheduledLoanInstallment : loanSchedule for (LoanSchedulePeriodData scheduledLoanPeriod : loanSchedule.getPeriods()) {
.getScheduledLoanInstallments()) { if (scheduledLoanPeriod.isRepaymentPeriod()) {
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
final MonetaryCurrency monetaryCurrency = new MonetaryCurrency( loan, scheduledLoanPeriod.periodNumber(),
scheduledLoanInstallment.getPrincipalDue().getCurrencyCode(), scheduledLoanPeriod.periodDueDate(),
scheduledLoanInstallment.getPrincipalDue().getDigitsAfterDecimal()); scheduledLoanPeriod.principalDue(),
scheduledLoanPeriod.interestDue());
Money principal = Money.of(monetaryCurrency,
scheduledLoanInstallment.getPrincipalDue().getAmount()); modifiedLoanRepaymentSchedule.add(installment);
}
Money interest = Money.of(monetaryCurrency,
scheduledLoanInstallment.getInterestDue().getAmount());
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
loan, scheduledLoanInstallment.getInstallmentNumber(),
// scheduledLoanInstallment.getPeriodStart(),
scheduledLoanInstallment.getPeriodEnd(), principal.getAmount(),
interest.getAmount());
modifiedLoanRepaymentSchedule.add(installment);
} }
loan.disburseWithModifiedRepaymentSchedule(actualDisbursementDate, modifiedLoanRepaymentSchedule, defaultLoanLifecycleStateMachine()); loan.disburseWithModifiedRepaymentSchedule(actualDisbursementDate, modifiedLoanRepaymentSchedule, defaultLoanLifecycleStateMachine());
} else { } else {
@@ -402,7 +392,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
if (loan.isActualDisbursedOnDateEarlierOrLaterThanExpected()) { if (loan.isActualDisbursedOnDateEarlierOrLaterThanExpected()) {
// FIXME - handle this use case - recalculate loan schedule using original settings. // FIXME - KW - handle this use case - recalculate loan schedule using original settings.
} }
loan.undoDisbursal(defaultLoanLifecycleStateMachine()); loan.undoDisbursal(defaultLoanLifecycleStateMachine());
@@ -7,17 +7,23 @@ import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData; import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData; import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
public interface AmortizationLoanScheduleGenerator { public interface AmortizationLoanScheduleGenerator {
LoanSchedule generate(LoanProductRelatedDetail loanScheduleInfo, LocalDate disbursementDate, @Deprecated
LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom, CurrencyData currencyData, LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo,
BigDecimal periodInterestRateForRepaymentPeriod, final LocalDate disbursementDate,
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, final LocalDate firstRepaymentDate,
List<LocalDate> scheduledDates); final LocalDate interestCalculatedFrom,
final CurrencyData currencyData,
final BigDecimal periodInterestRateForRepaymentPeriod,
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
final List<LocalDate> scheduledDates);
NewLoanScheduleData generate( NewLoanScheduleData generate(
ApplicationCurrency applicationCurrency,
LoanProductRelatedDetail loanScheduleInfo, LoanProductRelatedDetail loanScheduleInfo,
LocalDate disbursementDate, LocalDate disbursementDate,
LocalDate firstRepaymentDate, LocalDate firstRepaymentDate,
@@ -25,5 +31,4 @@ public interface AmortizationLoanScheduleGenerator {
BigDecimal periodInterestRateForRepaymentPeriod, BigDecimal periodInterestRateForRepaymentPeriod,
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
List<LocalDate> scheduledDates); List<LocalDate> scheduledDates);
}
}
@@ -7,6 +7,7 @@ import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData; import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData; import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.loan.domain.AmortizationMethod; import org.mifosng.platform.loan.domain.AmortizationMethod;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType; import org.mifosng.platform.loan.domain.PeriodFrequencyType;
@@ -36,7 +37,10 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
@Override @Override
public NewLoanScheduleData generate( public NewLoanScheduleData generate(
final LoanProductRelatedDetail loanScheduleInfo, final ApplicationCurrency applicationCurrency,
final LoanProductRelatedDetail loanScheduleInfo,
final Integer loanTermFrequency,
final PeriodFrequencyType loanTermFrequencyType,
final LocalDate disbursementDate, final LocalDate disbursementDate,
final LocalDate firstRepaymentDate, final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom) { final LocalDate interestCalculatedFrom) {
@@ -52,7 +56,7 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
// Determine with 'amortisation' approach to use // Determine with 'amortisation' approach to use
final AmortizationLoanScheduleGenerator generator = this.amortizationLoanScheduleGeneratorFactory.createGenerator(loanScheduleInfo.getAmortizationMethod()); final AmortizationLoanScheduleGenerator generator = this.amortizationLoanScheduleGeneratorFactory.createGenerator(loanScheduleInfo.getAmortizationMethod());
return generator.generate( return generator.generate(applicationCurrency,
loanScheduleInfo, loanScheduleInfo,
disbursementDate, disbursementDate,
firstRepaymentDate, firstRepaymentDate,
@@ -62,6 +66,7 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
scheduledDates); scheduledDates);
} }
@Deprecated
@Override @Override
public LoanSchedule generate( public LoanSchedule generate(
final LoanProductRelatedDetail loanScheduleInfo, final LoanProductRelatedDetail loanScheduleInfo,
@@ -13,6 +13,7 @@ import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.LoanSchedulePeriodData; import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment; import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
@@ -28,6 +29,7 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
@Override @Override
public NewLoanScheduleData generate( public NewLoanScheduleData generate(
final ApplicationCurrency currency,
final LoanProductRelatedDetail loanScheduleInfo, final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate, final LocalDate disbursementDate,
final LocalDate firstRepaymentDate, final LocalDate firstRepaymentDate,
@@ -36,10 +38,7 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
final List<LocalDate> scheduledDates) { final List<LocalDate> scheduledDates) {
// 1. add disbursements to loan schedule final Collection<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
// 2.
Collection<LoanSchedulePeriodData> periods = new ArrayList<LoanSchedulePeriodData>();
// determine 'total payment' for each repayment based on pmt function (and hence the total due overall) // determine 'total payment' for each repayment based on pmt function (and hence the total due overall)
final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency(); final MonetaryCurrency monetaryCurrency = loanScheduleInfo.getPrincipal().getCurrency();
@@ -58,11 +57,17 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
scheduledDates, scheduledDates,
idealDisbursementDateBasedOnFirstRepaymentDate); idealDisbursementDateBasedOnFirstRepaymentDate);
// create entries of disbursement period on loan schedule // create entries of disbursement period on loan schedule
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursement(disbursementDate, loanScheduleInfo.getPrincipal().getAmount()); final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursement(disbursementDate, loanScheduleInfo.getPrincipal().getAmount());
periods.add(disbursementPeriod); periods.add(disbursementPeriod);
int loanTermInDays = Integer.valueOf(0);
BigDecimal cumulativePrincipalDisbursed = loanScheduleInfo.getPrincipal().getAmount();
BigDecimal cumulativePrincipalDue = BigDecimal.ZERO;
BigDecimal cumulativeInterestExpected = BigDecimal.ZERO;
BigDecimal cumulativeChargesToDate = BigDecimal.ZERO;
BigDecimal totalExpectedRepayment = BigDecimal.ZERO;
LocalDate startDate = disbursementDate; LocalDate startDate = disbursementDate;
int periodNumber = 1; int periodNumber = 1;
for (LocalDate scheduledDueDate : scheduledDates) { for (LocalDate scheduledDueDate : scheduledDates) {
@@ -115,15 +120,31 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
interestForInstallment.getAmount(), totalInstallmentDue.getAmount()); interestForInstallment.getAmount(), totalInstallmentDue.getAmount());
periods.add(installment); periods.add(installment);
// handle cumulative fields
loanTermInDays += daysInPeriod;
cumulativePrincipalDue = cumulativePrincipalDue.add(principalForInstallment.getAmount());
cumulativeInterestExpected = cumulativeInterestExpected.add(interestForInstallment.getAmount());
totalExpectedRepayment = totalExpectedRepayment.add(totalInstallmentDue.getAmount());
startDate = scheduledDueDate; startDate = scheduledDueDate;
periodNumber++; periodNumber++;
} }
return new NewLoanScheduleData(periods); final BigDecimal cumulativePrincipalOutstanding = cumulativePrincipalDisbursed.subtract(cumulativePrincipalDue);
CurrencyData currencyData = new CurrencyData(
currency.getCode(),
currency.getName(),
monetaryCurrency.getDigitsAfterDecimal(),
currency.getDisplaySymbol(),
currency.getNameCode());
return new NewLoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment);
} }
@Deprecated
@Override @Override
public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo, public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate, final LocalDate disbursementDate,
@@ -2,6 +2,7 @@ package org.mifosng.platform.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.joda.time.Days; import org.joda.time.Days;
@@ -9,8 +10,10 @@ import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData; import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData; import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment; import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.currency.domain.MonetaryCurrency; import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
@@ -22,16 +25,106 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
@Override @Override
public NewLoanScheduleData generate( public NewLoanScheduleData generate(
ApplicationCurrency currency,
LoanProductRelatedDetail loanScheduleInfo, LoanProductRelatedDetail loanScheduleInfo,
LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate disbursementDate, LocalDate firstRepaymentDate,
LocalDate interestCalculatedFrom, LocalDate interestCalculatedFrom,
BigDecimal periodInterestRateForRepaymentPeriod, BigDecimal periodInterestRateForRepaymentPeriod,
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
List<LocalDate> scheduledDates) { List<LocalDate> scheduledDates) {
// TODO Auto-generated method stub
return null; 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 totalPrincipal = Money.zero(monetaryCurrency);
Money totalInterest = Money.zero(monetaryCurrency);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator.calculateRepaymentPeriodAsAFractionOfDays(loanScheduleInfo.getRepaymentPeriodFrequencyType(),
loanScheduleInfo.getRepayEvery(), interestCalculatedFrom, scheduledDates, idealDisbursementDateBasedOnFirstRepaymentDate);
// create entries of disbursement period on loan schedule
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursement(disbursementDate, loanScheduleInfo.getPrincipal().getAmount());
periods.add(disbursementPeriod);
int loanTermInDays = Integer.valueOf(0);
BigDecimal cumulativePrincipalDisbursed = loanScheduleInfo.getPrincipal().getAmount();
BigDecimal cumulativePrincipalDue = BigDecimal.ZERO;
BigDecimal cumulativeInterestExpected = BigDecimal.ZERO;
BigDecimal cumulativeChargesToDate = BigDecimal.ZERO;
BigDecimal totalExpectedRepayment = 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());
}
}
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment);
outstandingBalance = outstandingBalance.minus(principalForInstallment);
LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentPeriod(periodNumber, startDate,
scheduledDueDate,
principalForInstallment.getAmount(),
outstandingBalance.getAmount(),
interestForInstallment.getAmount(), totalInstallmentDue.getAmount());
periods.add(installment);
// handle cumulative fields
loanTermInDays += daysInPeriod;
cumulativePrincipalDue = cumulativePrincipalDue.add(principalForInstallment.getAmount());
cumulativeInterestExpected = cumulativeInterestExpected.add(interestForInstallment.getAmount());
totalExpectedRepayment = totalExpectedRepayment.add(totalInstallmentDue.getAmount());
startDate = scheduledDueDate;
periodNumber++;
}
final BigDecimal cumulativePrincipalOutstanding = cumulativePrincipalDisbursed.subtract(cumulativePrincipalDue);
CurrencyData currencyData = new CurrencyData(
currency.getCode(),
currency.getName(),
monetaryCurrency.getDigitsAfterDecimal(),
currency.getDisplaySymbol(),
currency.getNameCode());
return new NewLoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment);
} }
@Deprecated
@Override @Override
public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo, public LoanSchedule generate(final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate, final LocalDate disbursementDate,
@@ -4,14 +4,19 @@ import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData; import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData; import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.api.data.LoanSchedulePeriodData;
import org.mifosng.platform.api.data.MoneyData; import org.mifosng.platform.api.data.MoneyData;
import org.mifosng.platform.api.data.ScheduledLoanInstallment; import org.mifosng.platform.api.data.ScheduledLoanInstallment;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType; import org.mifosng.platform.loan.domain.PeriodFrequencyType;
@@ -23,10 +28,108 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
@Override @Override
public NewLoanScheduleData generate( public NewLoanScheduleData generate(
LoanProductRelatedDetail loanScheduleInfo, final ApplicationCurrency currency,
LocalDate disbursementDate, LocalDate firstRepaymentDate, final LoanProductRelatedDetail loanScheduleInfo,
LocalDate interestCalculatedFrom) { final Integer loanTermFrequency,
return null; final PeriodFrequencyType loanTermFrequencyType,
final LocalDate disbursementDate,
final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom) {
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 totalPrincipal = Money.zero(outstandingBalance.getCurrency());
Money totalInterest = Money.zero(outstandingBalance.getCurrency());
// create entries of disbursement period on loan schedule
final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursement(disbursementDate, loanScheduleInfo.getPrincipal().getAmount());
periods.add(disbursementPeriod);
int loanTermInDays = Integer.valueOf(0);
BigDecimal cumulativePrincipalDisbursed = loanScheduleInfo.getPrincipal().getAmount();
BigDecimal cumulativePrincipalDue = BigDecimal.ZERO;
BigDecimal cumulativeInterestExpected = BigDecimal.ZERO;
BigDecimal cumulativeChargesToDate = BigDecimal.ZERO;
BigDecimal totalExpectedRepayment = 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());
}
}
Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment);
outstandingBalance = outstandingBalance.minus(principalPerInstallment);
LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentPeriod(periodNumber, startDate,
scheduledDueDate,
principalPerInstallment.getAmount(),
outstandingBalance.getAmount(),
interestPerInstallment.getAmount(), totalInstallmentDue.getAmount());
periods.add(installment);
// handle cumulative fields
loanTermInDays += daysInPeriod;
cumulativePrincipalDue = cumulativePrincipalDue.add(principalPerInstallment.getAmount());
cumulativeInterestExpected = cumulativeInterestExpected.add(interestPerInstallment.getAmount());
totalExpectedRepayment = totalExpectedRepayment.add(totalInstallmentDue.getAmount());
startDate = scheduledDueDate;
periodNumber++;
}
final BigDecimal cumulativePrincipalOutstanding = cumulativePrincipalDisbursed.subtract(cumulativePrincipalDue);
CurrencyData currencyData = new CurrencyData(
currency.getCode(),
currency.getName(),
monetaryCurrency.getDigitsAfterDecimal(),
currency.getDisplaySymbol(),
currency.getNameCode());
return new NewLoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment);
} }
@Override @Override
@@ -4,17 +4,23 @@ import org.joda.time.LocalDate;
import org.mifosng.platform.api.NewLoanScheduleData; import org.mifosng.platform.api.NewLoanScheduleData;
import org.mifosng.platform.api.data.CurrencyData; import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.LoanSchedule; import org.mifosng.platform.api.data.LoanSchedule;
import org.mifosng.platform.currency.domain.ApplicationCurrency;
import org.mifosng.platform.loan.domain.LoanProductRelatedDetail; import org.mifosng.platform.loan.domain.LoanProductRelatedDetail;
import org.mifosng.platform.loan.domain.PeriodFrequencyType; import org.mifosng.platform.loan.domain.PeriodFrequencyType;
public interface LoanScheduleGenerator { public interface LoanScheduleGenerator {
@Deprecated
LoanSchedule generate(LoanProductRelatedDetail loanScheduleInfo, LoanSchedule generate(LoanProductRelatedDetail loanScheduleInfo,
Integer loanTermFrequency, Integer loanTermFrequency,
PeriodFrequencyType loanTermFrequencyType, PeriodFrequencyType loanTermFrequencyType,
LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom, LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom,
CurrencyData currencyData); CurrencyData currencyData);
NewLoanScheduleData generate(LoanProductRelatedDetail loanScheduleInfo, LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom); NewLoanScheduleData generate(ApplicationCurrency applicationCurrency,
LoanProductRelatedDetail loanScheduleInfo,
Integer loanTermFrequency,
PeriodFrequencyType loanTermFrequencyType,
LocalDate disbursementDate, LocalDate firstRepaymentDate, LocalDate interestCalculatedFrom);
} }