Inital support for adding loan charge due on specified date for demo to gk.

This commit is contained in:
Keith Woodlock
2012-10-28 11:29:57 +00:00
parent f4a9d183d7
commit 3d5c0d88c0
33 changed files with 683 additions and 428 deletions
@@ -0,0 +1,34 @@
alter table m_loan_charge
add column due_for_collection_as_of_date date DEFAULT NULL after charge_time_enum;
alter table m_loan_repayment_schedule
add column fee_charges_amount decimal(19,6) DEFAULT NULL after interest_writtenoff_derived;
alter table m_loan_repayment_schedule
add column fee_charges_completed_derived decimal(19,6) DEFAULT NULL after fee_charges_amount;
alter table m_loan_repayment_schedule
add column fee_charges_writtenoff_derived decimal(19,6) DEFAULT NULL after fee_charges_completed_derived;
alter table m_loan_repayment_schedule
add column fee_charges_waived_derived decimal(19,6) DEFAULT NULL after fee_charges_writtenoff_derived;
alter table m_loan_repayment_schedule
add column penalty_charges_amount decimal(19,6) DEFAULT NULL after fee_charges_waived_derived;
alter table m_loan_repayment_schedule
add column penalty_charges_completed_derived decimal(19,6) DEFAULT NULL after penalty_charges_amount;
alter table m_loan_repayment_schedule
add column penalty_charges_writtenoff_derived decimal(19,6) DEFAULT NULL after penalty_charges_completed_derived;
alter table m_loan_repayment_schedule
add column penalty_charges_waived_derived decimal(19,6) DEFAULT NULL after penalty_charges_writtenoff_derived;
/**
modify existing columns
*/
alter table `m_loan_repayment_schedule` MODIFY `duedate` date NOT NULL;
alter table `m_loan_repayment_schedule` MODIFY `installment` smallint(5) NOT NULL;
alter table `m_loan_repayment_schedule` MODIFY `interest_waived_derived` decimal(19,6) DEFAULT NULL;
@@ -311,7 +311,7 @@ public class LoansApiResource {
@Context final UriInfo uriInfo,
final String jsonRequestBody) {
LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(null, jsonRequestBody);
final LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(null, jsonRequestBody);
if (is(commandParam, "calculateLoanSchedule")) {
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
@@ -325,7 +325,7 @@ public class LoansApiResource {
private String calculateLoanSchedule(final UriInfo uriInfo, final CalculateLoanScheduleCommand command) {
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command);
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command);
final Set<String> typicalLoanScheduleResponseParameters = new HashSet<String>(
Arrays.asList("periods", "cumulativePrincipalDisbursed"));
@@ -3,6 +3,11 @@ package org.mifosng.platform.api.commands;
import java.math.BigDecimal;
import java.util.Set;
import org.joda.time.LocalDate;
/**
* Immutable command for creating and updating loan charges.
*/
public class LoanChargeCommand {
private final Long id;
@@ -11,18 +16,32 @@ public class LoanChargeCommand {
private final BigDecimal amount;
private final Integer chargeTimeType;
private final LocalDate specifiedDueDate;
private final Integer chargeCalculationType;
private final Set<String> modifiedParameters;
/**
* Used to capture what parameters were passed in the json api request.
* It does not indicate that these values are modified from their original values when tyring to update.
*/
private final Set<String> parametersPassedInCommand;
public LoanChargeCommand(Set<String> modifiedParameters, Long id, Long loanId, Long chargeId, BigDecimal amount, Integer chargeTimeType, Integer chargeCalculationType) {
this.modifiedParameters = modifiedParameters;
public LoanChargeCommand(
final Set<String> parametersPassedInCommand,
final Long id,
final Long loanId,
final Long chargeId,
final BigDecimal amount,
final Integer chargeTimeType,
final Integer chargeCalculationType,
final LocalDate specifiedDueDate) {
this.parametersPassedInCommand = parametersPassedInCommand;
this.id = id;
this.chargeId = chargeId;
this.loanId = loanId;
this.amount = amount;
this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType;
this.specifiedDueDate = specifiedDueDate;
}
public Long getId() {
@@ -44,20 +63,28 @@ public class LoanChargeCommand {
public Integer getChargeTimeType() {
return chargeTimeType;
}
public LocalDate getSpecifiedDueDate() {
return specifiedDueDate;
}
public Integer getChargeCalculationType() {
public Integer getChargeCalculationType() {
return chargeCalculationType;
}
public boolean isAmountChanged(){
return this.modifiedParameters.contains("amount");
return this.parametersPassedInCommand.contains("amount");
}
public boolean isChargeTimeTypeChanged(){
return this.modifiedParameters.contains("chargeTimeType");
return this.parametersPassedInCommand.contains("chargeTimeType");
}
public boolean isSpecifiedDueDateChanged(){
return this.parametersPassedInCommand.contains("specifiedDueDate");
}
public boolean isChargeCalculationTypeChanged(){
return this.modifiedParameters.contains("chargeCalculationType");
return this.parametersPassedInCommand.contains("chargeCalculationType");
}
}
}
@@ -3,6 +3,8 @@ package org.mifosng.platform.api.data;
import java.math.BigDecimal;
import java.util.Collection;
import org.joda.time.LocalDate;
/**
* Immutable data object for loan charge data.
*/
@@ -10,25 +12,39 @@ public class LoanChargeData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final Long chargeId;
@SuppressWarnings("unused")
private final String name;
@SuppressWarnings("unused")
private final EnumOptionData chargeTimeType;
@SuppressWarnings("unused")
private final LocalDate dueAsOfDate;
@SuppressWarnings("unused")
private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage;
@SuppressWarnings("unused")
private final BigDecimal amountPercentageAppliedTo;
@SuppressWarnings("unused")
private final CurrencyData currency;
@SuppressWarnings("unused")
private final BigDecimal amount;
@SuppressWarnings("unused")
private final BigDecimal amountPaid;
@SuppressWarnings("unused")
private final BigDecimal amountOutstanding;
@SuppressWarnings("unused")
private final BigDecimal amountOrPercentage;
@@ -61,6 +77,7 @@ public class LoanChargeData {
final BigDecimal amountPaid,
final BigDecimal amountOutstanding,
final EnumOptionData chargeTimeType,
final LocalDate dueAsOfDate,
final EnumOptionData chargeCalculationType,
final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo) {
@@ -72,6 +89,7 @@ public class LoanChargeData {
this.amountPaid = amountPaid;
this.amountOutstanding = amountOutstanding;
this.chargeTimeType = chargeTimeType;
this.dueAsOfDate = dueAsOfDate;
this.chargeCalculationType = chargeCalculationType;
this.percentage = percentage;
this.amountPercentageAppliedTo = amountPercentageAppliedTo;
@@ -102,6 +120,7 @@ public class LoanChargeData {
this.amountPaid = BigDecimal.ZERO;
this.amountOutstanding = amount;
this.chargeTimeType = chargeTimeType;
this.dueAsOfDate = null;
this.chargeCalculationType = chargeCalculationType;
this.percentage = null;
this.amountPercentageAppliedTo = null;
@@ -33,9 +33,13 @@ public class LoanSchedulePeriodData {
private final BigDecimal interestWrittenOff;
private final BigDecimal interestOutstanding;
private final BigDecimal chargesDue;
private final BigDecimal chargesPaid;
private final BigDecimal chargesOutstanding;
private final BigDecimal feeChargesDue;
private final BigDecimal feeChargesPaid;
@SuppressWarnings("unused")
private final BigDecimal feeChargesWaived;
@SuppressWarnings("unused")
private final BigDecimal feeChargesWrittenOff;
private final BigDecimal feeChargesOutstanding;
@SuppressWarnings("unused")
private final BigDecimal totalOriginalDueForPeriod;
@@ -60,11 +64,17 @@ public class LoanSchedulePeriodData {
return new LoanSchedulePeriodData(periodNumber, from, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement, isDisbursed);
}
public static LoanSchedulePeriodData repaymentOnlyPeriod(final Integer periodNumber,
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principalDue,
final BigDecimal principalOutstanding, final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal totalDueForPeriod) {
public static LoanSchedulePeriodData repaymentOnlyPeriod(
final Integer periodNumber,
final LocalDate fromDate,
final LocalDate dueDate,
final BigDecimal principalDue,
final BigDecimal principalOutstanding,
final BigDecimal interestDueOnPrincipalOutstanding,
final BigDecimal chargesDueForPeriod,
final BigDecimal totalDueForPeriod) {
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, principalDue, principalOutstanding, interestDueOnPrincipalOutstanding, totalDueForPeriod);
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, principalDue, principalOutstanding, interestDueOnPrincipalOutstanding, chargesDueForPeriod, totalDueForPeriod);
}
public static LoanSchedulePeriodData repaymentPeriodWithPayments(
@@ -82,6 +92,11 @@ public class LoanSchedulePeriodData {
final BigDecimal interestWaived,
final BigDecimal interestWrittenOff,
final BigDecimal interestOutstanding,
final BigDecimal feeChargesDue,
final BigDecimal feeChargesPaid,
final BigDecimal feeChargesWaived,
final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesOutstanding,
final BigDecimal totalDueForPeriod,
final BigDecimal totalPaid,
final BigDecimal totalWaived,
@@ -91,6 +106,7 @@ public class LoanSchedulePeriodData {
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate,
principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding,
feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding,
totalDueForPeriod, totalPaid, totalWaived, totalWrittenOff, totalOutstanding);
}
@@ -127,21 +143,25 @@ public class LoanSchedulePeriodData {
this.interestWrittenOff = null;
this.interestOutstanding = null;
this.chargesDue = chargesDueAtTimeOfDisbursement;
this.feeChargesDue = chargesDueAtTimeOfDisbursement;
if (isDisbursed) {
this.chargesPaid = chargesDueAtTimeOfDisbursement;
this.chargesOutstanding = BigDecimal.ZERO;
this.feeChargesPaid = chargesDueAtTimeOfDisbursement;
this.feeChargesWaived = BigDecimal.ZERO;
this.feeChargesWrittenOff = BigDecimal.ZERO;
this.feeChargesOutstanding = BigDecimal.ZERO;
} else {
this.chargesPaid = BigDecimal.ZERO;
this.chargesOutstanding = chargesDueAtTimeOfDisbursement;
this.feeChargesPaid = BigDecimal.ZERO;
this.feeChargesWaived = BigDecimal.ZERO;
this.feeChargesWrittenOff = BigDecimal.ZERO;
this.feeChargesOutstanding = chargesDueAtTimeOfDisbursement;
}
this.totalOriginalDueForPeriod = chargesDueAtTimeOfDisbursement;
this.totalDueForPeriod = chargesDueAtTimeOfDisbursement;
this.totalPaidForPeriod = this.chargesPaid;
this.totalPaidForPeriod = this.feeChargesPaid;
this.totalWaivedForPeriod = null;
this.totalWrittenOffForPeriod = null;
this.totalOutstandingForPeriod = this.chargesOutstanding;
this.totalOutstandingForPeriod = this.feeChargesOutstanding;
if (dueDate.isBefore(new LocalDate())) {
this.totalOverdue = this.totalOutstandingForPeriod;
} else {
@@ -156,6 +176,7 @@ public class LoanSchedulePeriodData {
final BigDecimal principalOriginalDue,
final BigDecimal principalOutstanding,
final BigDecimal interestDueOnPrincipalOutstanding,
final BigDecimal chargesDueForPeriod,
final BigDecimal totalDueForPeriod) {
this.period = periodNumber;
this.fromDate = fromDate;
@@ -180,9 +201,11 @@ public class LoanSchedulePeriodData {
this.interestWrittenOff = null;
this.interestOutstanding = interestDueOnPrincipalOutstanding;
this.chargesDue = BigDecimal.ZERO;
this.chargesPaid = null;
this.chargesOutstanding = null;
this.feeChargesDue = chargesDueForPeriod;
this.feeChargesPaid = null;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.feeChargesOutstanding = null;
this.totalOriginalDueForPeriod = totalDueForPeriod;
this.totalDueForPeriod = totalDueForPeriod;
@@ -214,6 +237,11 @@ public class LoanSchedulePeriodData {
final BigDecimal interestWaived,
final BigDecimal interestWrittenOff,
final BigDecimal interestOutstanding,
final BigDecimal feeChargesDue,
final BigDecimal feeChargesPaid,
final BigDecimal feeChargesWaived,
final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesOutstanding,
final BigDecimal totalDueForPeriod,
final BigDecimal totalPaid,
final BigDecimal totalWaived,
@@ -242,9 +270,11 @@ public class LoanSchedulePeriodData {
this.interestWrittenOff = interestWrittenOff;
this.interestOutstanding = interestOutstanding;
this.chargesDue = BigDecimal.ZERO;
this.chargesPaid = BigDecimal.ZERO;
this.chargesOutstanding = BigDecimal.ZERO;
this.feeChargesDue = feeChargesDue;
this.feeChargesPaid = feeChargesPaid;
this.feeChargesWaived = feeChargesWaived;
this.feeChargesWrittenOff = feeChargesWrittenOff;
this.feeChargesOutstanding = feeChargesOutstanding;
this.totalOriginalDueForPeriod = totalDueForPeriod;
this.totalDueForPeriod = totalDueForPeriod;
@@ -329,15 +359,15 @@ public class LoanSchedulePeriodData {
}
public BigDecimal chargesDue() {
return defaultToZeroIfNull(this.chargesDue);
return defaultToZeroIfNull(this.feeChargesDue);
}
public BigDecimal chargesPaid() {
return defaultToZeroIfNull(this.chargesPaid);
return defaultToZeroIfNull(this.feeChargesPaid);
}
public BigDecimal chargesOutstanding() {
return defaultToZeroIfNull(this.chargesOutstanding);
return defaultToZeroIfNull(this.feeChargesOutstanding);
}
public BigDecimal totalOverdue() {
@@ -465,67 +465,68 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
checkForUnsupportedParameters(requestMap, supportedParams);
final Set<String> modifiedParameters = new HashSet<String>();
final Set<String> parametersPassedInCommand = new HashSet<String>();
final JsonParser parser = new JsonParser();
final JsonElement element = parser.parse(json);
final JsonParserHelper helper = new JsonParserHelper();
final Long clientId = helper.extractLongNamed("clientId", element, modifiedParameters);
final Long groupId = helper.extractLongNamed("groupId", element, modifiedParameters);
final Long productId = helper.extractLongNamed("productId", element, 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);
final Long clientId = helper.extractLongNamed("clientId", element, parametersPassedInCommand);
final Long groupId = helper.extractLongNamed("groupId", element, parametersPassedInCommand);
final Long productId = helper.extractLongNamed("productId", element, parametersPassedInCommand);
final Long fundId = helper.extractLongNamed("fundId", element, parametersPassedInCommand);
final Long loanOfficerId = helper.extractLongNamed("loanOfficerId", element, parametersPassedInCommand);
final Long transactionProcessingStrategyId = helper.extractLongNamed("transactionProcessingStrategyId", element, parametersPassedInCommand);
final String externalId = helper.extractStringNamed("externalId", element, parametersPassedInCommand);
final BigDecimal principal = helper.extractBigDecimalNamed("principal", element, parametersPassedInCommand);
final BigDecimal inArrearsToleranceValue = helper.extractBigDecimalNamed("inArrearsTolerance", element, parametersPassedInCommand);
final BigDecimal interestRatePerPeriod = helper.extractBigDecimalNamed("interestRatePerPeriod", element, parametersPassedInCommand);
final Integer repaymentEvery = helper.extractIntegerNamed("repaymentEvery", element, 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);
final Integer repaymentEvery = helper.extractIntegerNamed("repaymentEvery", element, parametersPassedInCommand);
final Integer numberOfRepayments = helper.extractIntegerNamed("numberOfRepayments", element, parametersPassedInCommand);
final Integer repaymentFrequencyType = helper.extractIntegerNamed("repaymentFrequencyType", element, parametersPassedInCommand);
final Integer loanTermFrequency = helper.extractIntegerNamed("loanTermFrequency", element, parametersPassedInCommand);
final Integer loanTermFrequencyType = helper.extractIntegerNamed("loanTermFrequencyType", element, parametersPassedInCommand);
final Integer interestRateFrequencyType = helper.extractIntegerNamed("interestRateFrequencyType", element, parametersPassedInCommand);
final Integer amortizationType = helper.extractIntegerNamed("amortizationType", element, parametersPassedInCommand);
final Integer interestType = helper.extractIntegerNamed("interestType", element, parametersPassedInCommand);
final Integer interestCalculationPeriodType = helper.extractIntegerNamed("interestCalculationPeriodType", element, parametersPassedInCommand);
final LocalDate expectedDisbursementDate = helper.extractLocalDateNamed("expectedDisbursementDate", element, modifiedParameters);
final LocalDate repaymentsStartingFromDate = helper.extractLocalDateNamed("repaymentsStartingFromDate", element, modifiedParameters);
final LocalDate interestChargedFromDate = helper.extractLocalDateNamed("interestChargedFromDate", element, modifiedParameters);
final LocalDate submittedOnDate = helper.extractLocalDateNamed("submittedOnDate", element, modifiedParameters);
final LocalDate expectedDisbursementDate = helper.extractLocalDateNamed("expectedDisbursementDate", element, parametersPassedInCommand);
final LocalDate repaymentsStartingFromDate = helper.extractLocalDateNamed("repaymentsStartingFromDate", element, parametersPassedInCommand);
final LocalDate interestChargedFromDate = helper.extractLocalDateNamed("interestChargedFromDate", element, parametersPassedInCommand);
final LocalDate submittedOnDate = helper.extractLocalDateNamed("submittedOnDate", element, parametersPassedInCommand);
final String submittedOnNote = helper.extractStringNamed("submittedOnNote", element, modifiedParameters);
final String submittedOnNote = helper.extractStringNamed("submittedOnNote", element, parametersPassedInCommand);
LoanChargeCommand[] charges = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
if (object.has("charges") && object.get("charges").isJsonArray()) {
modifiedParameters.add("charges");
JsonArray array = object.get("charges").getAsJsonArray();
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = helper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = helper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) {
parametersPassedInCommand.add("charges");
final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
charges = new LoanChargeCommand[array.size()];
for (int i=0; i<array.size(); i++) {
JsonObject loanCharge = array.get(i).getAsJsonObject();
Map<String, Object> chargeRequestMap = gsonConverter.fromJson(loanCharge, typeOfMap);
if (requestMap.containsKey("locale")){
chargeRequestMap.put("locale", requestMap.get("locale"));
}
Set<String> chargeModifiedParameters = new HashSet<String>();
final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
final Set<String> chargeModifiedParameters = new HashSet<String>();
Long id = extractLongParameter("id", chargeRequestMap, chargeModifiedParameters);
Long chargeId = extractLongParameter("chargeId", chargeRequestMap, chargeModifiedParameters);
BigDecimal amount = extractBigDecimalParameter("amount", chargeRequestMap, chargeModifiedParameters);
Integer chargeTimeType = extractIntegerParameter("chargeTimeType", chargeRequestMap, chargeModifiedParameters);
Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", chargeRequestMap, chargeModifiedParameters);
charges[i] = new LoanChargeCommand(chargeModifiedParameters, id, null,chargeId, amount, chargeTimeType, chargeCalculationType);
final Long id = helper.extractLongNamed("id", loanChargeElement, chargeModifiedParameters);
final Long chargeId = helper.extractLongNamed("chargeId", loanChargeElement, chargeModifiedParameters);
final BigDecimal amount = helper.extractBigDecimalNamed("amount", loanChargeElement, locale, chargeModifiedParameters);
final Integer chargeTimeType = helper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale, chargeModifiedParameters);
final Integer chargeCalculationType = helper.extractIntegerNamed("chargeCalculationType", loanChargeElement, locale, chargeModifiedParameters);
final LocalDate specifiedDueDate = helper.extractLocalDateNamed("specifiedDueDate", loanChargeElement, dateFormat, locale, parametersPassedInCommand);
charges[i] = new LoanChargeCommand(chargeModifiedParameters, id, null, chargeId, amount, chargeTimeType, chargeCalculationType, specifiedDueDate);
}
}
}
return new LoanApplicationCommand(modifiedParameters,
return new LoanApplicationCommand(parametersPassedInCommand,
resourceIdentifier, clientId, groupId, productId, externalId, fundId, transactionProcessingStrategyId,
submittedOnDate, submittedOnNote,
expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate,
@@ -535,8 +536,9 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
inArrearsToleranceValue, charges,loanOfficerId);
}
// FIXME - KW - charges
@Override
public LoanChargeCommand convertJsonToLoanChargeCommand(Long loanChargeId, Long loanId, String json) {
public LoanChargeCommand convertJsonToLoanChargeCommand(final Long loanChargeId, final Long loanId, final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
@@ -544,8 +546,8 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> requestMap = gsonConverter.fromJson(json, typeOfMap);
Set<String> supportedParams = new HashSet<String>(
Arrays.asList("chargeId", "amount", "chargeTimeType", "chargeCalculationType", "locale")
final Set<String> supportedParams = new HashSet<String>(
Arrays.asList("chargeId", "amount", "chargeTimeType", "chargeCalculationType", "specifiedDueDate", "locale", "dateFormat")
);
checkForUnsupportedParameters(requestMap, supportedParams);
@@ -556,9 +558,10 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
BigDecimal amount = extractBigDecimalParameter("amount", requestMap, modifiedParameters);
Integer chargeTimeType = extractIntegerParameter("chargeTimeType", requestMap, modifiedParameters);
Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", requestMap, modifiedParameters);
final LocalDate submittedOnDate = extractLocalDateParameter("specifiedDueDate", requestMap, modifiedParameters);
return new LoanChargeCommand(modifiedParameters, loanChargeId, loanId, chargeId, amount,
chargeTimeType, chargeCalculationType);
chargeTimeType, chargeCalculationType, submittedOnDate);
}
@Override
@@ -62,8 +62,24 @@ public class JsonParserHelper {
return stringValue;
}
public BigDecimal extractBigDecimalNamed(final String parameterName,
final JsonElement element, final Set<String> modifiedParameters) {
public BigDecimal extractBigDecimalNamed(
final String parameterName,
final JsonElement element,
final Set<String> modifiedParameters) {
BigDecimal value = null;
if (element.isJsonObject()) {
final JsonObject object = element.getAsJsonObject();
final Locale locale = extractLocaleValue(object);
value = extractBigDecimalNamed(parameterName, object, locale, modifiedParameters);
}
return value;
}
public BigDecimal extractBigDecimalNamed(
final String parameterName,
final JsonObject element,
final Locale locale,
final Set<String> modifiedParameters) {
BigDecimal value = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
@@ -74,68 +90,115 @@ public class JsonParserHelper {
.getAsJsonPrimitive();
String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) {
value = convertFrom(valueAsString, parameterName,
extractLocaleValue(object));
value = convertFrom(valueAsString, parameterName, locale);
}
}
}
return value;
}
public Integer extractIntegerNamed(final String parameterName,
final JsonElement element, final Set<String> modifiedParameters) {
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()) {
final JsonObject object = element.getAsJsonObject();
final Locale locale = extractLocaleValue(object);
value = extractIntegerNamed(parameterName, object, locale, modifiedParameters);
}
return value;
}
public Integer extractIntegerNamed(
final String parameterName,
final JsonElement element,
final Locale locale,
final Set<String> modifiedParameters) {
Integer value = null;
if (element.isJsonObject()) {
final 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));
value = convertToInteger(valueAsString, parameterName, locale);
}
}
}
return value;
}
public String extractDateFormatParameter(final JsonObject element) {
String value = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
public LocalDate extractLocalDateNamed(final String parameterName,
final JsonElement element, final Set<String> modifiedParameters) {
final String dateFormatParameter = "dateFormat";
if (object.has(dateFormatParameter)
&& object.get(dateFormatParameter).isJsonPrimitive()) {
final JsonPrimitive primitive = object.get(dateFormatParameter).getAsJsonPrimitive();
value = primitive.getAsString();
}
}
return value;
}
public Locale extractLocaleParameter(final JsonObject element) {
Locale clientApplicationLocale = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
String locale = null;
final String localeParameter = "locale";
if (object.has(localeParameter)
&& object.get(localeParameter).isJsonPrimitive()) {
final JsonPrimitive primitive = object.get(localeParameter).getAsJsonPrimitive();
locale = primitive.getAsString();
clientApplicationLocale = localeFromString(locale);
}
}
return clientApplicationLocale;
}
public LocalDate extractLocalDateNamed(
final String parameterName,
final JsonElement element,
final Set<String> parametersPassedInCommand) {
LocalDate value = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
final String dateFormat = extractDateFormatParameter(object);
final Locale clientApplicationLocale = extractLocaleParameter(object);
value = extractLocalDateNamed(parameterName, object, dateFormat, clientApplicationLocale, parametersPassedInCommand);
}
return value;
}
public LocalDate extractLocalDateNamed(
final String parameterName,
final JsonObject element,
final String dateFormat,
final Locale clientApplicationLocale,
final Set<String> parametersPassedInCommand) {
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();
}
String locale = null;
Locale clientApplicationLocale = null;
final String localeParameter = "locale";
if (object.has(localeParameter)
&& object.get(localeParameter).isJsonPrimitive()) {
JsonPrimitive primitive = object.get(localeParameter)
.getAsJsonPrimitive();
locale = primitive.getAsString();
clientApplicationLocale = localeFromString(locale);
}
if (object.has(parameterName)
&& object.get(parameterName).isJsonPrimitive()) {
modifiedParameters.add(parameterName);
JsonPrimitive primitive = object.get(parameterName)
.getAsJsonPrimitive();
parametersPassedInCommand.add(parameterName);
final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
final String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) {
value = convertFrom(valueAsString, parameterName,
dateFormat, clientApplicationLocale);
value = convertFrom(valueAsString, parameterName, dateFormat, clientApplicationLocale);
}
}
@@ -356,156 +419,6 @@ public class JsonParserHelper {
}
}
/*
* 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)) {
@@ -3,7 +3,8 @@ package org.mifosng.platform.charge.domain;
public enum ChargeTimeType {
INVALID(0, "chargeTimeType.invalid"),
DISBURSEMENT(1, "chargeTimeType.disbursement");
DISBURSEMENT(1, "chargeTimeType.disbursement"),
SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate");
private final Integer value;
private final String code;
@@ -27,6 +28,9 @@ public enum ChargeTimeType {
case 1:
chargeTimeType = DISBURSEMENT;
break;
case 2:
chargeTimeType = SPECIFIED_DUE_DATE;
break;
default:
chargeTimeType = INVALID;
break;
@@ -12,11 +12,11 @@ public class ChargeCommandValidator {
private final ChargeCommand command;
public ChargeCommandValidator(ChargeCommand command) {
public ChargeCommandValidator(final ChargeCommand command) {
this.command = command;
}
public void validateForCreate(){
public void validateForCreate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
@@ -25,7 +25,7 @@ public class ChargeCommandValidator {
baseDataValidator.reset().parameter("amount").value(command.getAmount()).notNull().positiveAmount();
baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).notBlank();
baseDataValidator.reset().parameter("chargeAppliesTo").value(command.getChargeAppliesTo()).notNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).notNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).notNull().inMinMaxRange(1, 2);
baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).notNull().inMinMaxRange(1, 4);
if (!dataValidationErrors.isEmpty()) {
@@ -33,7 +33,7 @@ public class ChargeCommandValidator {
}
}
public void validateForUpdate(){
public void validateForUpdate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
@@ -43,7 +43,7 @@ public class ChargeCommandValidator {
baseDataValidator.reset().parameter("amount").value(command.getAmount()).ignoreIfNull().positiveAmount();
baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("chargeAppliesTo").value(command.getChargeAppliesTo()).ignoreIfNull().notNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).ignoreIfNull().notNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).ignoreIfNull().notNull().inMinMaxRange(1, 2);
baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).ignoreIfNull().notNull().inMinMaxRange(1, 4);
if (!dataValidationErrors.isEmpty()) {
@@ -33,6 +33,6 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
@Override
public List<EnumOptionData> retrieveCollectionTimeTypes() {
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT));
return Arrays.asList(chargeTimeType(ChargeTimeType.DISBURSEMENT), chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE));
}
}
@@ -11,12 +11,15 @@ public class ChargeEnumerations {
return chargeTimeType(ChargeTimeType.fromInt(id));
}
public static EnumOptionData chargeTimeType(ChargeTimeType type){
public static EnumOptionData chargeTimeType(final ChargeTimeType type){
EnumOptionData optionData = null;
switch (type){
case DISBURSEMENT:
optionData = new EnumOptionData(ChargeTimeType.DISBURSEMENT.getValue().longValue(), ChargeTimeType.DISBURSEMENT.getCode(), "Disbursement");
break;
case SPECIFIED_DUE_DATE:
optionData = new EnumOptionData(ChargeTimeType.SPECIFIED_DUE_DATE.getValue().longValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getCode(), "Specified due date");
break;
default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break;
@@ -28,7 +31,7 @@ public class ChargeEnumerations {
return chargeAppliesTo(ChargeAppliesTo.fromInt(id));
}
public static EnumOptionData chargeAppliesTo(ChargeAppliesTo type){
public static EnumOptionData chargeAppliesTo(final ChargeAppliesTo type){
EnumOptionData optionData = null;
switch (type){
case LOAN:
@@ -45,7 +48,7 @@ public class ChargeEnumerations {
return chargeCalculationType(ChargeCalculationType.fromInt(id));
}
public static EnumOptionData chargeCalculationType(ChargeCalculationType type){
public static EnumOptionData chargeCalculationType(final ChargeCalculationType type){
EnumOptionData optionData = null;
switch (type){
case FLAT:
@@ -6,6 +6,7 @@ import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.ChargeData;
import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.EnumOptionData;
@@ -177,6 +178,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
"lc.amount as amountDue, lc.amount_paid_derived as amountPaid, lc.amount_outstanding_derived as amountOutstanding, " +
"lc.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, " +
"lc.charge_time_enum as chargeTime, " +
"lc.due_for_collection_as_of_date as dueAsOfDate, " +
"lc.charge_calculation_enum as chargeCalculation, " +
"c.currency_code as currencyCode, oc.name as currencyName, " +
"oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " +
@@ -213,10 +215,12 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService
final int chargeTime = rs.getInt("chargeTime");
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final int chargeCalculation = rs.getInt("chargeCalculation");
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation);
return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountOutstanding, chargeTimeType, chargeCalculationType, percentageOf, amountPercentageAppliedTo);
return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo);
}
}
}
@@ -20,7 +20,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
private final ChargeRepository chargeRepository;
@Autowired
public ChargeWritePlatformServiceJpaRepositoryImpl(PlatformSecurityContext context, ChargeRepository chargeRepository) {
public ChargeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final ChargeRepository chargeRepository) {
this.context = context;
this.chargeRepository = chargeRepository;
}
@@ -31,21 +31,21 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
try {
this.context.authenticatedUser();
ChargeCommandValidator validator = new ChargeCommandValidator(command);
final ChargeCommandValidator validator = new ChargeCommandValidator(command);
validator.validateForCreate();
ChargeAppliesTo chargeAppliesTo = ChargeAppliesTo.fromInt(command.getChargeAppliesTo());
ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.getChargeTimeType());
ChargeCalculationType chargeCalculationType = ChargeCalculationType.fromInt(command.getChargeCalculationType());
final ChargeAppliesTo chargeAppliesTo = ChargeAppliesTo.fromInt(command.getChargeAppliesTo());
final ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.getChargeTimeType());
final ChargeCalculationType chargeCalculationType = ChargeCalculationType.fromInt(command.getChargeCalculationType());
Charge charge = Charge.createNew(command.getName(), command.getAmount(), command.getCurrencyCode(),
final Charge charge = Charge.createNew(command.getName(), command.getAmount(), command.getCurrencyCode(),
chargeAppliesTo, chargeTimeType, chargeCalculationType, command.isActive());
this.chargeRepository.saveAndFlush(charge);
return charge.getId();
} catch (DataIntegrityViolationException dve) {
handleFundDataIntegrityIssues(command, dve);
handleDataIntegrityIssues(command, dve);
return Long.valueOf(-1);
}
}
@@ -61,7 +61,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
validator.validateForUpdate();
final Long chargeId = command.getId();
Charge chargeForUpdate = this.chargeRepository.findOne(chargeId);
final Charge chargeForUpdate = this.chargeRepository.findOne(chargeId);
if (chargeForUpdate == null){
throw new ChargeNotFoundException(chargeId);
@@ -72,7 +72,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
return chargeForUpdate.getId();
} catch (DataIntegrityViolationException dve) {
handleFundDataIntegrityIssues(command, dve);
handleDataIntegrityIssues(command, dve);
return Long.valueOf(-1);
}
}
@@ -83,7 +83,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
this.context.authenticatedUser();
Charge chargeForDelete = this.chargeRepository.findOne(chargeId);
final Charge chargeForDelete = this.chargeRepository.findOne(chargeId);
if (chargeForDelete == null || chargeForDelete.isDeleted()){
throw new ChargeNotFoundException(chargeId);
}
@@ -97,7 +97,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
/*
* Guaranteed to throw an exception no matter what the data integrity issue is.
*/
private void handleFundDataIntegrityIssues(final ChargeCommand command, DataIntegrityViolationException dve) {
private void handleDataIntegrityIssues(final ChargeCommand command, final DataIntegrityViolationException dve) {
Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("name")) {
@@ -40,7 +40,7 @@ public class Money implements Comparable<Money>, Serializable {
}
public static Money of(final MonetaryCurrency currency, final BigDecimal newAmount) {
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), newAmount);
return new Money(currency.getCode(), currency.getDigitsAfterDecimal(), defaultToZeroIfNull(newAmount));
}
public static Money zero(final MonetaryCurrency currency) {
@@ -56,13 +56,19 @@ public class Money implements Comparable<Money>, Serializable {
private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount) {
this.currencyCode = currencyCode;
this.currencyDigitsAfterDecimal = digitsAfterDecimal;
if (amount != null) {
BigDecimal amountStripped = amount.stripTrailingZeros();
this.amount = amountStripped.setScale(this.currencyDigitsAfterDecimal, RoundingMode.HALF_EVEN);
} else {
this.amount = BigDecimal.ZERO;
}
final BigDecimal amountZeroed = defaultToZeroIfNull(amount);
BigDecimal amountStripped = amountZeroed.stripTrailingZeros();
this.amount = amountStripped.setScale(this.currencyDigitsAfterDecimal, RoundingMode.HALF_EVEN);
}
private static BigDecimal defaultToZeroIfNull(final BigDecimal value) {
BigDecimal result = BigDecimal.ZERO;
if (value != null) {
result = value;
}
return result;
}
public Money copy() {
return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros());
@@ -0,0 +1,12 @@
package org.mifosng.platform.exceptions;
/**
* {@link AbstractPlatformDomainRuleException} thrown when currency mismatch occurs
*/
public class LoanChargeWithoutMandatoryFieldException extends AbstractPlatformDomainRuleException {
public LoanChargeWithoutMandatoryFieldException(final String entity, final String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
super("error.msg."+entity+"."+postFix+".cannot.be.blank", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -1,5 +1,6 @@
package org.mifosng.platform.infrastructure;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
@@ -36,7 +37,20 @@ public class JdbcSupport {
return (Long) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Long.class);
}
public static Integer getInteger(ResultSet rs, String columnName) throws SQLException {
public static Integer getInteger(final ResultSet rs, final String columnName) throws SQLException {
return (Integer) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Integer.class);
}
public static BigDecimal getBigDecimalDefaultToZeroIfNull(final ResultSet rs, final String columnName) throws SQLException {
final BigDecimal value = rs.getBigDecimal(columnName);
return defaultToZeroIfNull(value);
}
private static BigDecimal defaultToZeroIfNull(final BigDecimal value) {
BigDecimal result = BigDecimal.ZERO;
if (value != null) {
result = value;
}
return result;
}
}
@@ -263,6 +263,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this.charges.add(loanCharge);
updateTotalChargesDueAtDisbursement();
// reprocess loan schedule based on charge been removed.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges, this.loanTransactions);
}
public void removeLoanCharge(final LoanCharge loanCharge) {
@@ -270,6 +274,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (removed) {
updateTotalChargesDueAtDisbursement();
}
// reprocess loan schedule based on charge been removed.
LoanScheduleWrapper wrapper = new LoanScheduleWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, this.charges, this.loanTransactions);
}
public void updateLoanCharge(final LoanCharge loanCharge, final LoanChargeCommand command) {
@@ -373,7 +381,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this, scheduledLoanInstallment.periodNumber(),
scheduledLoanInstallment.periodDueDate(),
scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.interestDue());
scheduledLoanInstallment.interestDue(),
scheduledLoanInstallment.chargesDue());
this.addRepaymentScheduleInstallment(installment);
}
}
@@ -3,6 +3,7 @@ package org.mifosng.platform.loan.domain;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@@ -11,11 +12,15 @@ import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.charge.domain.Charge;
import org.mifosng.platform.charge.domain.ChargeCalculationType;
import org.mifosng.platform.charge.domain.ChargeTimeType;
import org.mifosng.platform.exceptions.LoanChargeWithoutMandatoryFieldException;
import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity
@@ -32,6 +37,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Column(name = "charge_time_enum", nullable = false)
private Integer chargeTime;
@Temporal(TemporalType.DATE)
@Column(name = "due_for_collection_as_of_date")
private Date dueForCollectionAsOfDate;
@Column(name = "charge_calculation_enum")
private Integer chargeCalculation;
@@ -80,13 +89,25 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.loan = null;
this.charge = chargeDefinition;
if (command.isChargeTimeTypeChanged()){
if (command.isChargeTimeTypeChanged()) {
this.chargeTime = command.getChargeTimeType();
} else {
this.chargeTime = chargeDefinition.getChargeTime();
}
if (ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE)) {
if (command.getSpecifiedDueDate() == null) {
final String defaultUserMessage = "Loan charge is missing specified due date";
throw new LoanChargeWithoutMandatoryFieldException("loancharge", "specifiedDueDate", defaultUserMessage, command.getId(), chargeDefinition.getName());
}
this.dueForCollectionAsOfDate = command.getSpecifiedDueDate().toDate();
} else {
this.dueForCollectionAsOfDate = null;
}
if (command.isChargeCalculationTypeChanged()){
if (command.isChargeCalculationTypeChanged()) {
this.chargeCalculation = command.getChargeCalculationType();
} else {
this.chargeCalculation = chargeDefinition.getChargeCalculation();
@@ -110,6 +131,20 @@ public class LoanCharge extends AbstractPersistable<Long> {
} else {
this.chargeTime = chargeDefinition.getChargeTime();
}
if (ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE)) {
if (command.getSpecifiedDueDate() == null) {
final String defaultUserMessage = "Loan charge is missing specified due date";
throw new LoanChargeWithoutMandatoryFieldException(
"loancharge", "specifiedDueDate", defaultUserMessage,
command.getId(), chargeDefinition.getName());
}
this.dueForCollectionAsOfDate = command.getSpecifiedDueDate().toDate();
} else {
this.dueForCollectionAsOfDate = null;
}
if (command.isChargeCalculationTypeChanged()){
this.chargeCalculation = command.getChargeCalculationType();
@@ -226,54 +261,24 @@ public class LoanCharge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT);
}
// public BigDecimal calculateMonetaryAmount(final Money principalDisbursed) {
// BigDecimal calculatedAmount = BigDecimal.ZERO;
//
// switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
// case INVALID:
// calculatedAmount = this.amount;
// break;
// case FLAT:
// calculatedAmount = this.amount;
// break;
// case PERCENT_OF_AMOUNT:
// this.amountPercentageAppliedTo = principalDisbursed.getAmount();
// this.amount = percentageOf(this.amountPercentageAppliedTo, this.percentage);
// this.amountPaid = null;
// this.amountOutstanding = calculateOutstanding();
// calculatedAmount = this.amount;
// break;
// case PERCENT_OF_AMOUNT_AND_INTEREST:
// this.percentage = null;
// this.amount = null;
// this.amountPercentageAppliedTo = null;
// this.amountPaid = null;
// this.amountOutstanding = null;
// break;
// case PERCENT_OF_INTEREST:
// this.percentage = null;
// this.amount = null;
// this.amountPercentageAppliedTo = null;
// this.amountPaid = null;
// this.amountOutstanding = null;
// break;
// }
//
// return calculatedAmount;
// }
//
// public BigDecimal calculateMonetaryAmount() {
// return calculateMonetaryAmount(this.loan.getPrincpal());
// }
private boolean isGreaterThanZero(final BigDecimal value) {
return value.compareTo(BigDecimal.ZERO) == 1;
}
public LoanChargeCommand toData() {
Set<String> modifiedParameters = new HashSet<String>();
public LoanChargeCommand toCommand() {
final LocalDate specifiedDueDate = getDueForCollectionAsOfLocalDate();
final Set<String> modifiedParameters = new HashSet<String>();
return new LoanChargeCommand(modifiedParameters, this.getId(), this.loan.getId(), this.charge.getId(),
this.amount, this.chargeTime, this.chargeCalculation);
this.amount, this.chargeTime, this.chargeCalculation, specifiedDueDate);
}
private LocalDate getDueForCollectionAsOfLocalDate() {
LocalDate specifiedDueDate = null;
if (this.dueForCollectionAsOfDate != null) {
specifiedDueDate = new LocalDate(this.dueForCollectionAsOfDate);
}
return specifiedDueDate;
}
private boolean determineIfFullyPaid() {
@@ -326,4 +331,17 @@ public class LoanCharge extends AbstractPersistable<Long> {
public boolean hasLoanIdentifiedBy(final Long loanId) {
return this.loan.hasIdentifyOf(loanId);
}
public boolean isDueForCollectionBetween(final LocalDate fromNotInclusive, final LocalDate toInclusive) {
final LocalDate specifiedDueDate = getDueForCollectionAsOfLocalDate();
return specifiedDueDateFallsWithinPeriod(fromNotInclusive, toInclusive, specifiedDueDate);
}
private boolean specifiedDueDateFallsWithinPeriod(
final LocalDate fromNotInclusive,
final LocalDate toInclusive,
final LocalDate specifiedDueDate) {
return specifiedDueDate != null && fromNotInclusive.isBefore(specifiedDueDate) && (toInclusive.isAfter(specifiedDueDate) || toInclusive.isEqual(specifiedDueDate));
}
}
@@ -29,25 +29,49 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
private final Integer installmentNumber;
@Column(name = "principal_amount", scale=6, precision=19)
private BigDecimal principal = BigDecimal.ZERO;
private BigDecimal principal;
@Column(name = "interest_amount", scale=6, precision=19)
private BigDecimal interest = BigDecimal.ZERO;
private BigDecimal interest;
@Column(name = "fee_charges_amount", scale=6, precision=19)
private BigDecimal feeCharges;
@Column(name = "fee_charges_completed_derived", scale=6, precision=19)
private BigDecimal feeChargesCompleted;
@Column(name = "fee_charges_writtenoff_derived", scale=6, precision=19)
private BigDecimal feeChargesWrittenOff;
@Column(name = "fee_charges_waived_derived", scale=6, precision=19)
private BigDecimal feeChargesWaived;
@Column(name = "penalty_charges_amount", scale=6, precision=19)
private BigDecimal penaltyCharges;
@Column(name = "penalty_charges_completed_derived", scale=6, precision=19)
private BigDecimal penaltyChargesCompleted;
@Column(name = "penalty_charges_writtenoff_derived", scale=6, precision=19)
private BigDecimal penaltyChargesWrittenOff;
@Column(name = "penalty_charges_waived_derived", scale=6, precision=19)
private BigDecimal penaltyChargesWaived;
@Column(name = "principal_completed_derived", scale=6, precision=19)
private BigDecimal principalCompleted = BigDecimal.ZERO;
private BigDecimal principalCompleted;
@Column(name = "principal_writtenoff_derived", scale=6, precision=19)
private BigDecimal principalWrittenOff = BigDecimal.ZERO;
private BigDecimal principalWrittenOff;
@Column(name = "interest_completed_derived", scale = 6, precision = 19)
private BigDecimal interestCompleted = BigDecimal.ZERO;
private BigDecimal interestCompleted;
@Column(name = "interest_waived_derived", scale = 6, precision = 19)
private BigDecimal interestWaived = BigDecimal.ZERO;
private BigDecimal interestWaived;
@Column(name = "interest_writtenoff_derived", scale=6, precision=19)
private BigDecimal interestWrittenOff = BigDecimal.ZERO;
private BigDecimal interestWrittenOff;
@Column(name="completed_derived")
private boolean completed;
@@ -67,19 +91,31 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.completed = false;
}
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber,
final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest) {
public LoanRepaymentScheduleInstallment(
final Loan loan,
final Integer installmentNumber,
final LocalDate dueDate,
final BigDecimal principal,
final BigDecimal interest,
final BigDecimal feeCharges) {
this.loan = loan;
this.installmentNumber = installmentNumber;
this.dueDate = dueDate.toDateMidnight().toDate();
this.principal = principal;
this.principalCompleted = BigDecimal.ZERO;
this.interest = interest;
this.interestCompleted = BigDecimal.ZERO;
this.principal = defaultToNullIfZero(principal);
this.interest = defaultToNullIfZero(interest);
this.feeCharges = defaultToNullIfZero(feeCharges);
this.completed = false;
}
public Loan getLoan() {
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
BigDecimal result = value;
if (BigDecimal.ZERO.compareTo(value) == 0) {
result = null;
}
return result;
}
public Loan getLoan() {
return this.loan;
}
@@ -128,6 +164,27 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
final Money interestAccountedFor = getInterestCompleted(currency).plus(getInterestWaived(currency)).plus(getInterestWrittenOff(currency));
return getInterest(currency).minus(interestAccountedFor);
}
public Money getFeeCharges(final MonetaryCurrency currency) {
return Money.of(currency, this.feeCharges);
}
public Money getFeeChargesCompleted(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesCompleted);
}
public Money getFeeChargesWaived(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesWaived);
}
public Money getFeeChargesWrittenOff(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesWrittenOff);
}
public Money getFeeChargesOutstanding(final MonetaryCurrency currency) {
final Money feeChargesAccountedFor = getFeeChargesCompleted(currency).plus(getFeeChargesWaived(currency)).plus(getFeeChargesWrittenOff(currency));
return getFeeCharges(currency).minus(feeChargesAccountedFor);
}
public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterestOutstanding(currency).isGreaterThanZero();
@@ -247,4 +304,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public boolean isOverdueOn(final LocalDate transactionDate) {
return this.getDueDate().isBefore(transactionDate);
}
public void updateChargePortion(final Money feeChargesDue) {
this.feeCharges = defaultToNullIfZero(feeChargesDue.getAmount());
}
}
@@ -1,7 +1,9 @@
package org.mifosng.platform.loan.domain;
import java.util.List;
import java.util.Set;
import org.joda.time.LocalDate;
import org.mifosng.platform.currency.domain.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money;
@@ -24,4 +26,44 @@ public class LoanScheduleWrapper {
return cumulativeValue;
}
/**
* FIXME - this is not a side effect free approach so move this into a specific wrapper than makes this obvious from the read-only wrapper.
* @param localDate
*/
public void reprocess(
final MonetaryCurrency currency,
final LocalDate disbursementDate,
final List<LoanRepaymentScheduleInstallment> repaymentPeriods,
final Set<LoanCharge> loanCharges,
final List<LoanTransaction> transactions) {
LocalDate startDate = disbursementDate;
for (LoanRepaymentScheduleInstallment period : repaymentPeriods) {
// FIXME - kw - also need to handle case where some of these charges are paid/writtenoff/waived so that those components are updated on repayment period.
final Money feeChargesDueForRepaymentPeriod = cumulativeChargesDueWithin(startDate, period.getDueDate(), loanCharges, currency);
// if (feeChargesDueForRepaymentPeriod.isGreaterThanZero()) {
period.updateChargePortion(feeChargesDueForRepaymentPeriod);
// }
startDate = period.getDueDate();
}
}
private Money cumulativeChargesDueWithin(
final LocalDate periodStart,
final LocalDate periodEnd,
final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency) {
Money cumulative = Money.zero(monetaryCurrency);
for (LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount());
}
}
return cumulative;
}
}
@@ -83,6 +83,17 @@ public class CalculateLoanScheduleCommandValidator {
}
}
// if (command.getCharges() != null) {
// for (LoanChargeCommand loanCharge : command.getCharges()) {
// try {
// LoanChargeCommandValidator validator = new LoanChargeCommandValidator(loanCharge);
// validator.validateForCreate();
// } catch (PlatformApiDataValidationException e) {
// dataValidationErrors.addAll(e.getErrors());
// }
// }
// }
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
@@ -5,5 +5,5 @@ import org.mifosng.platform.api.commands.CalculateLoanScheduleCommand;
public interface CalculationPlatformService {
LoanScheduleData calculateLoanScheduleNew(CalculateLoanScheduleCommand command);
LoanScheduleData calculateLoanSchedule(CalculateLoanScheduleCommand command);
}
@@ -40,7 +40,7 @@ public class CalculationPlatformServiceImpl implements CalculationPlatformServic
}
@Override
public LoanScheduleData calculateLoanScheduleNew(final CalculateLoanScheduleCommand command) {
public LoanScheduleData calculateLoanSchedule(final CalculateLoanScheduleCommand command) {
context.authenticatedUser();
@@ -84,19 +84,21 @@ public class LoanAssembler {
}
Client client = null;
Group group = null;
if (command.getClientId() != null) {
client = this.clientRepository.findOne(command.getClientId());
if (client == null || client.isDeleted()) {
throw new ClientNotFoundException(command.getClientId());
}
} else {
}
Group group = null;
if (command.getGroupId() != null) {
group = this.groupRepository.findOne(command.getGroupId());
if (group == null || group.isDeleted()) {
throw new GroupNotFoundException(command.getGroupId());
}
}
final MonetaryCurrency currency = loanProduct.getCurrency();
final Integer loanTermFrequency = command.getLoanTermFrequency();
final PeriodFrequencyType loanTermFrequencyType = PeriodFrequencyType.fromInt(command.getLoanTermFrequencyType());
@@ -113,24 +115,34 @@ public class LoanAssembler {
// optionally, see if charges are associated with loan on creation (through loan product or by being directly added)
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanProduct.getCharges(), command.getPrincipal());
Loan loan;
Loan loan = null;
if (group != null) {
loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, group, loanRepaymentScheduleDetail, loanCharges);
loan.setExternalId(command.getExternalId());
}
if (client != null) {
loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges);
} else {
loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, group, loanRepaymentScheduleDetail, loanCharges);
loan.setExternalId(command.getExternalId());
}
if (loan == null) {
// FIXME - kw - put in appropriate exception here.
throw new RuntimeException();
}
loan.setExternalId(command.getExternalId());
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand());
for (LoanSchedulePeriodData scheduledLoanInstallment : loanSchedule.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) {
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
loan, scheduledLoanInstallment.periodNumber(),
scheduledLoanInstallment.periodDueDate(),
scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.interestDue());
scheduledLoanInstallment.interestDue(),
scheduledLoanInstallment.chargesDue());
loan.addRepaymentScheduleInstallment(installment);
}
}
@@ -1,6 +1,5 @@
package org.mifosng.platform.loan.service;
import org.mifosng.platform.DataValidatorBuilder;
import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.data.ApiParameterError;
@@ -13,36 +12,42 @@ public class LoanChargeCommandValidator {
private final LoanChargeCommand command;
public LoanChargeCommandValidator(LoanChargeCommand command) {
public LoanChargeCommandValidator(final LoanChargeCommand command) {
this.command = command;
}
public void validateForCreate(){
public void validateForCreate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
baseDataValidator.reset().parameter("chargeId").value(command.getChargeId()).notNull().longGreaterThanZero();
baseDataValidator.reset().parameter("amount").value(command.getAmount()).ignoreIfNull().positiveAmount();
baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).ignoreIfNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).ignoreIfNull().inMinMaxRange(1, 4);
baseDataValidator.reset().parameter("amount").value(command.getAmount()).notNull().positiveAmount();
// baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).ignoreIfNull().inMinMaxRange(1, 2);
//
// if (command.getChargeTimeType().equals(Integer.valueOf(2))) {
// // date must be provided
// baseDataValidator.reset().parameter("specifiedDueDate").value(command.getSpecifiedDueDate()).notNull();
// }
//
// baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).ignoreIfNull().inMinMaxRange(1, 4);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
public void validateForUpdate(){
public void validateForUpdate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
baseDataValidator.reset().parameter("amount").value(command.getAmount()).ignoreIfNull().positiveAmount();
baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).ignoreIfNull().inMinMaxRange(1, 1);
baseDataValidator.reset().parameter("chargeTimeType").value(command.getChargeTimeType()).ignoreIfNull().inMinMaxRange(1, 2);
baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).ignoreIfNull().inMinMaxRange(1, 4);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
}
}
}
}
@@ -507,18 +507,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private static final class LoanScheduleMapper implements RowMapper<LoanSchedulePeriodData> {
private LocalDate lastDueDate;
private BigDecimal outstandingLoanBalance;
private BigDecimal outstandingLoanPrincipalBalance;
public LoanScheduleMapper(final DisbursementData disbursement) {
this.lastDueDate = disbursement.disbursementDate();
this.outstandingLoanBalance = disbursement.amount();
this.outstandingLoanPrincipalBalance = disbursement.amount();
}
public String loanScheduleSchema() {
return " ls.loan_id as loanId, ls.installment as period, ls.duedate as dueDate, "
+ " ls.principal_amount as principalDue, ls.principal_completed_derived as principalPaid, ls.principal_writtenoff_derived as principalWrittenOff, "
+ " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived, ls.interest_writtenoff_derived as interestWrittenOff "
+ " ls.interest_amount as interestDue, ls.interest_completed_derived as interestPaid, ls.interest_waived_derived as interestWaived, ls.interest_writtenoff_derived as interestWrittenOff, "
+ " ls.fee_charges_amount as feeChargesDue, ls.fee_charges_completed_derived as feeChargesPaid, ls.fee_charges_waived_derived as feeChargesWaived, ls.fee_charges_writtenoff_derived as feeChargesWrittenOff "
+ " from m_loan l "
+ " join m_loan_repayment_schedule ls on ls.loan_id = l.id ";
}
@@ -529,47 +530,46 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Long loanId = rs.getLong("loanId");
final Integer period = JdbcSupport.getInteger(rs, "period");
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
final BigDecimal principalDue = rs.getBigDecimal("principalDue");
final BigDecimal principalPaid = rs.getBigDecimal("principalPaid");
BigDecimal principalWrittenOff = rs.getBigDecimal("principalWrittenOff");
if (principalWrittenOff == null) {
principalWrittenOff = BigDecimal.ZERO;
}
final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDue");
final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
// TODO - KW - rather than calculate this here should we put calculation on derived column on table like other columns?
final BigDecimal principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff);
final BigDecimal interestDue = rs.getBigDecimal("interestDue");
final BigDecimal interestPaid = rs.getBigDecimal("interestPaid");
BigDecimal interestWaived = rs.getBigDecimal("interestWaived");
if (interestWaived == null) {
interestWaived = BigDecimal.ZERO;
}
BigDecimal interestWrittenOff = rs.getBigDecimal("interestWrittenOff");
if (interestWrittenOff == null) {
interestWrittenOff = BigDecimal.ZERO;
}
final BigDecimal interestDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue");
final BigDecimal interestPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestPaid");
final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
// TODO - KW - rather than calculate this here should we put calculation on derived column on table like other columns?
final BigDecimal interestOutstanding = interestDue.subtract(interestPaid).subtract(interestWaived).subtract(interestWrittenOff);
final BigDecimal feeChargesDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesDue");
final BigDecimal feeChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesPaid");
final BigDecimal feeChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWaived");
final BigDecimal feeChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWrittenOff");
final BigDecimal feeChargesOutstanding = feeChargesDue.subtract(feeChargesPaid).subtract(feeChargesWaived).subtract(feeChargesWrittenOff);
final BigDecimal totalDueForPeriod = principalDue.add(interestDue);
final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid);
final BigDecimal totalWaivedForPeriod = interestWaived;
final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff);
final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding);
final BigDecimal totalDueForPeriod = principalDue.add(interestDue).add(feeChargesDue);
final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid);
final BigDecimal totalWaivedForPeriod = interestWaived.add(feeChargesWaived);
final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff).add(feeChargesWrittenOff);
final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding).add(feeChargesOutstanding);
final LocalDate fromDate = this.lastDueDate;
final BigDecimal outstandingPrincipleBalanceOfLoan = outstandingLoanBalance.subtract(principalDue);
final BigDecimal outstandingPrincipleBalanceOfLoan = outstandingLoanPrincipalBalance.subtract(principalDue);
// update based on current period values
this.lastDueDate = dueDate;
outstandingLoanBalance = outstandingLoanBalance.subtract(principalDue);
outstandingLoanPrincipalBalance = outstandingLoanPrincipalBalance.subtract(principalDue);
return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate,
principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipleBalanceOfLoan, interestDue,
interestPaid, interestWaived, interestWrittenOff, interestOutstanding, totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod,
totalOutstandingForPeriod);
principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipleBalanceOfLoan,
interestDue, interestPaid, interestWaived, interestWrittenOff, interestOutstanding,
feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding,
totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, totalOutstandingForPeriod);
}
}
@@ -162,7 +162,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
final Set<LoanCharge> charges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanProduct.getCharges(), loan.getPrincpal().getAmount());
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand());
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand());
loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, loanOfficer, defaultLoanLifecycleStateMachine());
this.loanRepository.save(loan);
@@ -361,7 +361,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Set<LoanCharge> loanCharges = loan.getCharges();
List<LoanChargeCommand> commands = new ArrayList<LoanChargeCommand>();
for (LoanCharge loanCharge : loanCharges) {
commands.add(loanCharge.toData());
commands.add(loanCharge.toCommand());
}
LoanChargeCommand[] loanChargeCommands = commands.toArray(new LoanChargeCommand[commands.size()]);
@@ -374,7 +374,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanTermFrequency, loanTermFrequencyType,
actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate, loanChargeCommands);
LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(calculateCommand);
LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateCommand);
List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule = new ArrayList<LoanRepaymentScheduleInstallment>();
@@ -384,7 +384,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan, scheduledLoanPeriod.periodNumber(),
scheduledLoanPeriod.periodDueDate(),
scheduledLoanPeriod.principalDue(),
scheduledLoanPeriod.interestDue());
scheduledLoanPeriod.interestDue(),
scheduledLoanPeriod.chargesDue());
modifiedLoanRepaymentSchedule.add(installment);
}
@@ -16,7 +16,6 @@ public interface AmortizationLoanScheduleGenerator {
ApplicationCurrency applicationCurrency,
LoanProductRelatedDetail loanScheduleInfo,
LocalDate disbursementDate,
LocalDate firstRepaymentDate,
LocalDate interestCalculatedFrom,
BigDecimal periodInterestRateForRepaymentPeriod,
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
@@ -60,7 +60,6 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
return generator.generate(applicationCurrency,
loanScheduleInfo,
disbursementDate,
firstRepaymentDate,
interestCalculatedFrom,
periodInterestRateForRepaymentPeriod,
idealDisbursementDateBasedOnFirstRepaymentDate,
@@ -31,7 +31,6 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
final ApplicationCurrency currency,
final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate,
final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom,
final BigDecimal periodInterestRateForRepaymentPeriod,
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
@@ -60,7 +59,6 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
for (LoanCharge loanCharge : loanCharges) {
// FIXME - KW - right now only charges at disbursement are supported.
if (loanCharge.isDueAtDisbursement()) {
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
}
@@ -119,15 +117,19 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
}
}
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment);
outstandingBalance = outstandingBalance.minus(principalForInstallment);
Money chargesForInstallment = cumulativeChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency);
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment).plus(chargesForInstallment);
cumulativeChargesToDate = cumulativeChargesToDate.add(chargesForInstallment.getAmount());
LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate,
scheduledDueDate,
principalForInstallment.getAmount(),
outstandingBalance.getAmount(),
interestForInstallment.getAmount(), totalInstallmentDue.getAmount());
interestForInstallment.getAmount(),
chargesForInstallment.getAmount(),
totalInstallmentDue.getAmount());
periods.add(installment);
@@ -153,4 +155,21 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment);
}
private Money cumulativeChargesDueWithin(
final LocalDate periodStart,
final LocalDate periodEnd,
final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency) {
Money cumulative = Money.zero(monetaryCurrency);
for (LoanCharge loanCharge : loanCharges) {
if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount());
}
}
return cumulative;
}
}
@@ -27,7 +27,6 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
final ApplicationCurrency currency,
final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate,
final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom,
final BigDecimal periodInterestRateForRepaymentPeriod,
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
@@ -50,7 +49,6 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
for (LoanCharge loanCharge : loanCharges) {
// FIXME - KW - right now only charges at disbursement are supported.
if (loanCharge.isDueAtDisbursement()) {
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
}
@@ -100,15 +98,21 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
}
}
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment);
outstandingBalance = outstandingBalance.minus(principalForInstallment);
// FIXME - KW - charges
// Money chargesForInstallment = cumulativeChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency);
Money chargesForInstallment = Money.zero(monetaryCurrency);
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment).plus(chargesForInstallment);
cumulativeChargesToDate = cumulativeChargesToDate.add(chargesForInstallment.getAmount());
LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate,
scheduledDueDate,
principalForInstallment.getAmount(),
outstandingBalance.getAmount(),
interestForInstallment.getAmount(), totalInstallmentDue.getAmount());
interestForInstallment.getAmount(),
chargesForInstallment.getAmount(),
totalInstallmentDue.getAmount());
periods.add(installment);
@@ -65,7 +65,6 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
for (LoanCharge loanCharge : loanCharges) {
// FIXME - KW - right now only charges at disbursement are supported.
if (loanCharge.isDueAtDisbursement()) {
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
}
@@ -108,14 +107,20 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
}
}
Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment);
outstandingBalance = outstandingBalance.minus(principalPerInstallment);
// Money chargesForInstallment = cumulativeChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency);
Money chargesForInstallment = Money.zero(monetaryCurrency);
Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment).plus(chargesForInstallment);
cumulativeChargesToDate = cumulativeChargesToDate.add(chargesForInstallment.getAmount());
LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate,
scheduledDueDate,
principalPerInstallment.getAmount(),
outstandingBalance.getAmount(),
interestPerInstallment.getAmount(), totalInstallmentDue.getAmount());
interestPerInstallment.getAmount(),
chargesForInstallment.getAmount(),
totalInstallmentDue.getAmount());
periods.add(installment);
@@ -12,6 +12,7 @@ public class LoanRepaymentScheduleInstallmentBuilder {
private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build();
private Money principal = new MoneyBuilder().build();
private Money interest = new MoneyBuilder().build();
private Money feeCharges = new MoneyBuilder().build();
private boolean completed = false;
public LoanRepaymentScheduleInstallmentBuilder(MonetaryCurrency currencyDetail) {
@@ -21,7 +22,7 @@ public class LoanRepaymentScheduleInstallmentBuilder {
}
public LoanRepaymentScheduleInstallment build() {
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, installmentNumber, dueDate, principal.getAmount(), interest.getAmount());
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, installmentNumber, dueDate, principal.getAmount(), interest.getAmount(), feeCharges.getAmount());
if (completed) {
installment.payPrincipalComponent(principal);
installment.payInterestComponent(interest);