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, @Context final UriInfo uriInfo,
final String jsonRequestBody) { final String jsonRequestBody) {
LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(null, jsonRequestBody); final LoanApplicationCommand command = this.apiDataConversionService.convertJsonToLoanApplicationCommand(null, jsonRequestBody);
if (is(commandParam, "calculateLoanSchedule")) { if (is(commandParam, "calculateLoanSchedule")) {
CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand(); CalculateLoanScheduleCommand calculateLoanScheduleCommand = command.toCalculateLoanScheduleCommand();
@@ -325,7 +325,7 @@ public class LoansApiResource {
private String calculateLoanSchedule(final UriInfo uriInfo, final CalculateLoanScheduleCommand command) { 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>( final Set<String> typicalLoanScheduleResponseParameters = new HashSet<String>(
Arrays.asList("periods", "cumulativePrincipalDisbursed")); Arrays.asList("periods", "cumulativePrincipalDisbursed"));
@@ -3,6 +3,11 @@ package org.mifosng.platform.api.commands;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.Set; import java.util.Set;
import org.joda.time.LocalDate;
/**
* Immutable command for creating and updating loan charges.
*/
public class LoanChargeCommand { public class LoanChargeCommand {
private final Long id; private final Long id;
@@ -11,18 +16,32 @@ public class LoanChargeCommand {
private final BigDecimal amount; private final BigDecimal amount;
private final Integer chargeTimeType; private final Integer chargeTimeType;
private final LocalDate specifiedDueDate;
private final Integer chargeCalculationType; 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) { public LoanChargeCommand(
this.modifiedParameters = modifiedParameters; 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.id = id;
this.chargeId = chargeId; this.chargeId = chargeId;
this.loanId = loanId; this.loanId = loanId;
this.amount = amount; this.amount = amount;
this.chargeTimeType = chargeTimeType; this.chargeTimeType = chargeTimeType;
this.chargeCalculationType = chargeCalculationType; this.chargeCalculationType = chargeCalculationType;
this.specifiedDueDate = specifiedDueDate;
} }
public Long getId() { public Long getId() {
@@ -45,19 +64,27 @@ public class LoanChargeCommand {
return chargeTimeType; return chargeTimeType;
} }
public Integer getChargeCalculationType() { public LocalDate getSpecifiedDueDate() {
return specifiedDueDate;
}
public Integer getChargeCalculationType() {
return chargeCalculationType; return chargeCalculationType;
} }
public boolean isAmountChanged(){ public boolean isAmountChanged(){
return this.modifiedParameters.contains("amount"); return this.parametersPassedInCommand.contains("amount");
} }
public boolean isChargeTimeTypeChanged(){ public boolean isChargeTimeTypeChanged(){
return this.modifiedParameters.contains("chargeTimeType"); return this.parametersPassedInCommand.contains("chargeTimeType");
}
public boolean isSpecifiedDueDateChanged(){
return this.parametersPassedInCommand.contains("specifiedDueDate");
} }
public boolean isChargeCalculationTypeChanged(){ 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.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import org.joda.time.LocalDate;
/** /**
* Immutable data object for loan charge data. * Immutable data object for loan charge data.
*/ */
@@ -10,25 +12,39 @@ public class LoanChargeData {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Long id; private final Long id;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final Long chargeId; private final Long chargeId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final String name; private final String name;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final EnumOptionData chargeTimeType; private final EnumOptionData chargeTimeType;
@SuppressWarnings("unused")
private final LocalDate dueAsOfDate;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final EnumOptionData chargeCalculationType; private final EnumOptionData chargeCalculationType;
private final BigDecimal percentage; private final BigDecimal percentage;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final BigDecimal amountPercentageAppliedTo; private final BigDecimal amountPercentageAppliedTo;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final CurrencyData currency; private final CurrencyData currency;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final BigDecimal amount; private final BigDecimal amount;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final BigDecimal amountPaid; private final BigDecimal amountPaid;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final BigDecimal amountOutstanding; private final BigDecimal amountOutstanding;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final BigDecimal amountOrPercentage; private final BigDecimal amountOrPercentage;
@@ -61,6 +77,7 @@ public class LoanChargeData {
final BigDecimal amountPaid, final BigDecimal amountPaid,
final BigDecimal amountOutstanding, final BigDecimal amountOutstanding,
final EnumOptionData chargeTimeType, final EnumOptionData chargeTimeType,
final LocalDate dueAsOfDate,
final EnumOptionData chargeCalculationType, final EnumOptionData chargeCalculationType,
final BigDecimal percentage, final BigDecimal percentage,
final BigDecimal amountPercentageAppliedTo) { final BigDecimal amountPercentageAppliedTo) {
@@ -72,6 +89,7 @@ public class LoanChargeData {
this.amountPaid = amountPaid; this.amountPaid = amountPaid;
this.amountOutstanding = amountOutstanding; this.amountOutstanding = amountOutstanding;
this.chargeTimeType = chargeTimeType; this.chargeTimeType = chargeTimeType;
this.dueAsOfDate = dueAsOfDate;
this.chargeCalculationType = chargeCalculationType; this.chargeCalculationType = chargeCalculationType;
this.percentage = percentage; this.percentage = percentage;
this.amountPercentageAppliedTo = amountPercentageAppliedTo; this.amountPercentageAppliedTo = amountPercentageAppliedTo;
@@ -102,6 +120,7 @@ public class LoanChargeData {
this.amountPaid = BigDecimal.ZERO; this.amountPaid = BigDecimal.ZERO;
this.amountOutstanding = amount; this.amountOutstanding = amount;
this.chargeTimeType = chargeTimeType; this.chargeTimeType = chargeTimeType;
this.dueAsOfDate = null;
this.chargeCalculationType = chargeCalculationType; this.chargeCalculationType = chargeCalculationType;
this.percentage = null; this.percentage = null;
this.amountPercentageAppliedTo = null; this.amountPercentageAppliedTo = null;
@@ -33,9 +33,13 @@ public class LoanSchedulePeriodData {
private final BigDecimal interestWrittenOff; private final BigDecimal interestWrittenOff;
private final BigDecimal interestOutstanding; private final BigDecimal interestOutstanding;
private final BigDecimal chargesDue; private final BigDecimal feeChargesDue;
private final BigDecimal chargesPaid; private final BigDecimal feeChargesPaid;
private final BigDecimal chargesOutstanding; @SuppressWarnings("unused")
private final BigDecimal feeChargesWaived;
@SuppressWarnings("unused")
private final BigDecimal feeChargesWrittenOff;
private final BigDecimal feeChargesOutstanding;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final BigDecimal totalOriginalDueForPeriod; private final BigDecimal totalOriginalDueForPeriod;
@@ -60,11 +64,17 @@ public class LoanSchedulePeriodData {
return new LoanSchedulePeriodData(periodNumber, from, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement, isDisbursed); return new LoanSchedulePeriodData(periodNumber, from, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement, isDisbursed);
} }
public static LoanSchedulePeriodData repaymentOnlyPeriod(final Integer periodNumber, public static LoanSchedulePeriodData repaymentOnlyPeriod(
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principalDue, final Integer periodNumber,
final BigDecimal principalOutstanding, final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal totalDueForPeriod) { 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( public static LoanSchedulePeriodData repaymentPeriodWithPayments(
@@ -82,6 +92,11 @@ public class LoanSchedulePeriodData {
final BigDecimal interestWaived, final BigDecimal interestWaived,
final BigDecimal interestWrittenOff, final BigDecimal interestWrittenOff,
final BigDecimal interestOutstanding, final BigDecimal interestOutstanding,
final BigDecimal feeChargesDue,
final BigDecimal feeChargesPaid,
final BigDecimal feeChargesWaived,
final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesOutstanding,
final BigDecimal totalDueForPeriod, final BigDecimal totalDueForPeriod,
final BigDecimal totalPaid, final BigDecimal totalPaid,
final BigDecimal totalWaived, final BigDecimal totalWaived,
@@ -91,6 +106,7 @@ public class LoanSchedulePeriodData {
return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate,
principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding,
feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding,
totalDueForPeriod, totalPaid, totalWaived, totalWrittenOff, totalOutstanding); totalDueForPeriod, totalPaid, totalWaived, totalWrittenOff, totalOutstanding);
} }
@@ -127,21 +143,25 @@ public class LoanSchedulePeriodData {
this.interestWrittenOff = null; this.interestWrittenOff = null;
this.interestOutstanding = null; this.interestOutstanding = null;
this.chargesDue = chargesDueAtTimeOfDisbursement; this.feeChargesDue = chargesDueAtTimeOfDisbursement;
if (isDisbursed) { if (isDisbursed) {
this.chargesPaid = chargesDueAtTimeOfDisbursement; this.feeChargesPaid = chargesDueAtTimeOfDisbursement;
this.chargesOutstanding = BigDecimal.ZERO; this.feeChargesWaived = BigDecimal.ZERO;
this.feeChargesWrittenOff = BigDecimal.ZERO;
this.feeChargesOutstanding = BigDecimal.ZERO;
} else { } else {
this.chargesPaid = BigDecimal.ZERO; this.feeChargesPaid = BigDecimal.ZERO;
this.chargesOutstanding = chargesDueAtTimeOfDisbursement; this.feeChargesWaived = BigDecimal.ZERO;
this.feeChargesWrittenOff = BigDecimal.ZERO;
this.feeChargesOutstanding = chargesDueAtTimeOfDisbursement;
} }
this.totalOriginalDueForPeriod = chargesDueAtTimeOfDisbursement; this.totalOriginalDueForPeriod = chargesDueAtTimeOfDisbursement;
this.totalDueForPeriod = chargesDueAtTimeOfDisbursement; this.totalDueForPeriod = chargesDueAtTimeOfDisbursement;
this.totalPaidForPeriod = this.chargesPaid; this.totalPaidForPeriod = this.feeChargesPaid;
this.totalWaivedForPeriod = null; this.totalWaivedForPeriod = null;
this.totalWrittenOffForPeriod = null; this.totalWrittenOffForPeriod = null;
this.totalOutstandingForPeriod = this.chargesOutstanding; this.totalOutstandingForPeriod = this.feeChargesOutstanding;
if (dueDate.isBefore(new LocalDate())) { if (dueDate.isBefore(new LocalDate())) {
this.totalOverdue = this.totalOutstandingForPeriod; this.totalOverdue = this.totalOutstandingForPeriod;
} else { } else {
@@ -156,6 +176,7 @@ public class LoanSchedulePeriodData {
final BigDecimal principalOriginalDue, final BigDecimal principalOriginalDue,
final BigDecimal principalOutstanding, final BigDecimal principalOutstanding,
final BigDecimal interestDueOnPrincipalOutstanding, final BigDecimal interestDueOnPrincipalOutstanding,
final BigDecimal chargesDueForPeriod,
final BigDecimal totalDueForPeriod) { final BigDecimal totalDueForPeriod) {
this.period = periodNumber; this.period = periodNumber;
this.fromDate = fromDate; this.fromDate = fromDate;
@@ -180,9 +201,11 @@ public class LoanSchedulePeriodData {
this.interestWrittenOff = null; this.interestWrittenOff = null;
this.interestOutstanding = interestDueOnPrincipalOutstanding; this.interestOutstanding = interestDueOnPrincipalOutstanding;
this.chargesDue = BigDecimal.ZERO; this.feeChargesDue = chargesDueForPeriod;
this.chargesPaid = null; this.feeChargesPaid = null;
this.chargesOutstanding = null; this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.feeChargesOutstanding = null;
this.totalOriginalDueForPeriod = totalDueForPeriod; this.totalOriginalDueForPeriod = totalDueForPeriod;
this.totalDueForPeriod = totalDueForPeriod; this.totalDueForPeriod = totalDueForPeriod;
@@ -214,6 +237,11 @@ public class LoanSchedulePeriodData {
final BigDecimal interestWaived, final BigDecimal interestWaived,
final BigDecimal interestWrittenOff, final BigDecimal interestWrittenOff,
final BigDecimal interestOutstanding, final BigDecimal interestOutstanding,
final BigDecimal feeChargesDue,
final BigDecimal feeChargesPaid,
final BigDecimal feeChargesWaived,
final BigDecimal feeChargesWrittenOff,
final BigDecimal feeChargesOutstanding,
final BigDecimal totalDueForPeriod, final BigDecimal totalDueForPeriod,
final BigDecimal totalPaid, final BigDecimal totalPaid,
final BigDecimal totalWaived, final BigDecimal totalWaived,
@@ -242,9 +270,11 @@ public class LoanSchedulePeriodData {
this.interestWrittenOff = interestWrittenOff; this.interestWrittenOff = interestWrittenOff;
this.interestOutstanding = interestOutstanding; this.interestOutstanding = interestOutstanding;
this.chargesDue = BigDecimal.ZERO; this.feeChargesDue = feeChargesDue;
this.chargesPaid = BigDecimal.ZERO; this.feeChargesPaid = feeChargesPaid;
this.chargesOutstanding = BigDecimal.ZERO; this.feeChargesWaived = feeChargesWaived;
this.feeChargesWrittenOff = feeChargesWrittenOff;
this.feeChargesOutstanding = feeChargesOutstanding;
this.totalOriginalDueForPeriod = totalDueForPeriod; this.totalOriginalDueForPeriod = totalDueForPeriod;
this.totalDueForPeriod = totalDueForPeriod; this.totalDueForPeriod = totalDueForPeriod;
@@ -329,15 +359,15 @@ public class LoanSchedulePeriodData {
} }
public BigDecimal chargesDue() { public BigDecimal chargesDue() {
return defaultToZeroIfNull(this.chargesDue); return defaultToZeroIfNull(this.feeChargesDue);
} }
public BigDecimal chargesPaid() { public BigDecimal chargesPaid() {
return defaultToZeroIfNull(this.chargesPaid); return defaultToZeroIfNull(this.feeChargesPaid);
} }
public BigDecimal chargesOutstanding() { public BigDecimal chargesOutstanding() {
return defaultToZeroIfNull(this.chargesOutstanding); return defaultToZeroIfNull(this.feeChargesOutstanding);
} }
public BigDecimal totalOverdue() { public BigDecimal totalOverdue() {
@@ -465,67 +465,68 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
checkForUnsupportedParameters(requestMap, supportedParams); checkForUnsupportedParameters(requestMap, supportedParams);
final Set<String> modifiedParameters = new HashSet<String>(); final Set<String> parametersPassedInCommand = new HashSet<String>();
final JsonParser parser = new JsonParser(); final JsonParser parser = new JsonParser();
final JsonElement element = parser.parse(json); final JsonElement element = parser.parse(json);
final JsonParserHelper helper = new JsonParserHelper(); final JsonParserHelper helper = new JsonParserHelper();
final Long clientId = helper.extractLongNamed("clientId", element, modifiedParameters); final Long clientId = helper.extractLongNamed("clientId", element, parametersPassedInCommand);
final Long groupId = helper.extractLongNamed("groupId", element, modifiedParameters); final Long groupId = helper.extractLongNamed("groupId", element, parametersPassedInCommand);
final Long productId = helper.extractLongNamed("productId", element, modifiedParameters); final Long productId = helper.extractLongNamed("productId", element, parametersPassedInCommand);
final Long fundId = helper.extractLongNamed("fundId", element, modifiedParameters); final Long fundId = helper.extractLongNamed("fundId", element, parametersPassedInCommand);
final Long loanOfficerId = helper.extractLongNamed("loanOfficerId", element, modifiedParameters); final Long loanOfficerId = helper.extractLongNamed("loanOfficerId", element, parametersPassedInCommand);
final Long transactionProcessingStrategyId = helper.extractLongNamed("transactionProcessingStrategyId", element, modifiedParameters); final Long transactionProcessingStrategyId = helper.extractLongNamed("transactionProcessingStrategyId", element, parametersPassedInCommand);
final String externalId = helper.extractStringNamed("externalId", element, modifiedParameters); final String externalId = helper.extractStringNamed("externalId", element, parametersPassedInCommand);
final BigDecimal principal = helper.extractBigDecimalNamed("principal", element, modifiedParameters); final BigDecimal principal = helper.extractBigDecimalNamed("principal", element, parametersPassedInCommand);
final BigDecimal inArrearsToleranceValue = helper.extractBigDecimalNamed("inArrearsTolerance", element, modifiedParameters); final BigDecimal inArrearsToleranceValue = helper.extractBigDecimalNamed("inArrearsTolerance", element, parametersPassedInCommand);
final BigDecimal interestRatePerPeriod = helper.extractBigDecimalNamed("interestRatePerPeriod", element, modifiedParameters); final BigDecimal interestRatePerPeriod = helper.extractBigDecimalNamed("interestRatePerPeriod", element, parametersPassedInCommand);
final Integer repaymentEvery = helper.extractIntegerNamed("repaymentEvery", element, modifiedParameters); final Integer repaymentEvery = helper.extractIntegerNamed("repaymentEvery", element, parametersPassedInCommand);
final Integer numberOfRepayments = helper.extractIntegerNamed("numberOfRepayments", element, modifiedParameters); final Integer numberOfRepayments = helper.extractIntegerNamed("numberOfRepayments", element, parametersPassedInCommand);
final Integer repaymentFrequencyType = helper.extractIntegerNamed("repaymentFrequencyType", element, modifiedParameters); final Integer repaymentFrequencyType = helper.extractIntegerNamed("repaymentFrequencyType", element, parametersPassedInCommand);
final Integer loanTermFrequency = helper.extractIntegerNamed("loanTermFrequency", element, modifiedParameters); final Integer loanTermFrequency = helper.extractIntegerNamed("loanTermFrequency", element, parametersPassedInCommand);
final Integer loanTermFrequencyType = helper.extractIntegerNamed("loanTermFrequencyType", element, modifiedParameters); final Integer loanTermFrequencyType = helper.extractIntegerNamed("loanTermFrequencyType", element, parametersPassedInCommand);
final Integer interestRateFrequencyType = helper.extractIntegerNamed("interestRateFrequencyType", element, modifiedParameters); final Integer interestRateFrequencyType = helper.extractIntegerNamed("interestRateFrequencyType", element, parametersPassedInCommand);
final Integer amortizationType = helper.extractIntegerNamed("amortizationType", element, modifiedParameters); final Integer amortizationType = helper.extractIntegerNamed("amortizationType", element, parametersPassedInCommand);
final Integer interestType = helper.extractIntegerNamed("interestType", element, modifiedParameters); final Integer interestType = helper.extractIntegerNamed("interestType", element, parametersPassedInCommand);
final Integer interestCalculationPeriodType = helper.extractIntegerNamed("interestCalculationPeriodType", element, modifiedParameters); final Integer interestCalculationPeriodType = helper.extractIntegerNamed("interestCalculationPeriodType", element, parametersPassedInCommand);
final LocalDate expectedDisbursementDate = helper.extractLocalDateNamed("expectedDisbursementDate", element, modifiedParameters); final LocalDate expectedDisbursementDate = helper.extractLocalDateNamed("expectedDisbursementDate", element, parametersPassedInCommand);
final LocalDate repaymentsStartingFromDate = helper.extractLocalDateNamed("repaymentsStartingFromDate", element, modifiedParameters); final LocalDate repaymentsStartingFromDate = helper.extractLocalDateNamed("repaymentsStartingFromDate", element, parametersPassedInCommand);
final LocalDate interestChargedFromDate = helper.extractLocalDateNamed("interestChargedFromDate", element, modifiedParameters); final LocalDate interestChargedFromDate = helper.extractLocalDateNamed("interestChargedFromDate", element, parametersPassedInCommand);
final LocalDate submittedOnDate = helper.extractLocalDateNamed("submittedOnDate", element, modifiedParameters); 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; LoanChargeCommand[] charges = null;
if (element.isJsonObject()) { if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject(); final JsonObject topLevelJsonElement = element.getAsJsonObject();
if (object.has("charges") && object.get("charges").isJsonArray()) { final String dateFormat = helper.extractDateFormatParameter(topLevelJsonElement);
modifiedParameters.add("charges"); final Locale locale = helper.extractLocaleParameter(topLevelJsonElement);
JsonArray array = object.get("charges").getAsJsonArray(); if (topLevelJsonElement.has("charges") && topLevelJsonElement.get("charges").isJsonArray()) {
parametersPassedInCommand.add("charges");
final JsonArray array = topLevelJsonElement.get("charges").getAsJsonArray();
charges = new LoanChargeCommand[array.size()]; charges = new LoanChargeCommand[array.size()];
for (int i=0; i<array.size(); i++) { 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>();
Long id = extractLongParameter("id", chargeRequestMap, chargeModifiedParameters); final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
Long chargeId = extractLongParameter("chargeId", chargeRequestMap, chargeModifiedParameters); final Set<String> chargeModifiedParameters = new HashSet<String>();
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, resourceIdentifier, clientId, groupId, productId, externalId, fundId, transactionProcessingStrategyId,
submittedOnDate, submittedOnNote, submittedOnDate, submittedOnNote,
expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate, expectedDisbursementDate, repaymentsStartingFromDate, interestChargedFromDate,
@@ -535,8 +536,9 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
inArrearsToleranceValue, charges,loanOfficerId); inArrearsToleranceValue, charges,loanOfficerId);
} }
// FIXME - KW - charges
@Override @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)) { if (StringUtils.isBlank(json)) {
throw new InvalidJsonException(); throw new InvalidJsonException();
} }
@@ -544,8 +546,8 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType(); Type typeOfMap = new TypeToken<Map<String, String>>(){}.getType();
Map<String, String> requestMap = gsonConverter.fromJson(json, typeOfMap); Map<String, String> requestMap = gsonConverter.fromJson(json, typeOfMap);
Set<String> supportedParams = new HashSet<String>( final Set<String> supportedParams = new HashSet<String>(
Arrays.asList("chargeId", "amount", "chargeTimeType", "chargeCalculationType", "locale") Arrays.asList("chargeId", "amount", "chargeTimeType", "chargeCalculationType", "specifiedDueDate", "locale", "dateFormat")
); );
checkForUnsupportedParameters(requestMap, supportedParams); checkForUnsupportedParameters(requestMap, supportedParams);
@@ -556,9 +558,10 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService {
BigDecimal amount = extractBigDecimalParameter("amount", requestMap, modifiedParameters); BigDecimal amount = extractBigDecimalParameter("amount", requestMap, modifiedParameters);
Integer chargeTimeType = extractIntegerParameter("chargeTimeType", requestMap, modifiedParameters); Integer chargeTimeType = extractIntegerParameter("chargeTimeType", requestMap, modifiedParameters);
Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", requestMap, modifiedParameters); Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", requestMap, modifiedParameters);
final LocalDate submittedOnDate = extractLocalDateParameter("specifiedDueDate", requestMap, modifiedParameters);
return new LoanChargeCommand(modifiedParameters, loanChargeId, loanId, chargeId, amount, return new LoanChargeCommand(modifiedParameters, loanChargeId, loanId, chargeId, amount,
chargeTimeType, chargeCalculationType); chargeTimeType, chargeCalculationType, submittedOnDate);
} }
@Override @Override
@@ -62,8 +62,24 @@ public class JsonParserHelper {
return stringValue; return stringValue;
} }
public BigDecimal extractBigDecimalNamed(final String parameterName, public BigDecimal extractBigDecimalNamed(
final JsonElement element, final Set<String> modifiedParameters) { 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; BigDecimal value = null;
if (element.isJsonObject()) { if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject(); JsonObject object = element.getAsJsonObject();
@@ -74,68 +90,115 @@ public class JsonParserHelper {
.getAsJsonPrimitive(); .getAsJsonPrimitive();
String valueAsString = primitive.getAsString(); String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) { if (StringUtils.isNotBlank(valueAsString)) {
value = convertFrom(valueAsString, parameterName, value = convertFrom(valueAsString, parameterName, locale);
extractLocaleValue(object));
} }
} }
} }
return value; return value;
} }
public Integer extractIntegerNamed(final String parameterName, public Integer extractIntegerNamed(
final JsonElement element, final Set<String> modifiedParameters) { final String parameterName,
final JsonElement element,
final Set<String> modifiedParameters) {
Integer value = null; Integer value = null;
if (element.isJsonObject()) { if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject(); final JsonObject object = element.getAsJsonObject();
if (object.has(parameterName) final Locale locale = extractLocaleValue(object);
&& object.get(parameterName).isJsonPrimitive()) { 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); modifiedParameters.add(parameterName);
JsonPrimitive primitive = object.get(parameterName) JsonPrimitive primitive = object.get(parameterName)
.getAsJsonPrimitive(); .getAsJsonPrimitive();
String valueAsString = primitive.getAsString(); String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) { if (StringUtils.isNotBlank(valueAsString)) {
value = convertToInteger(valueAsString, parameterName, value = convertToInteger(valueAsString, parameterName, locale);
extractLocaleValue(object));
} }
} }
} }
return value; return value;
} }
public LocalDate extractLocalDateNamed(final String parameterName, public String extractDateFormatParameter(final JsonObject element) {
final JsonElement element, final Set<String> modifiedParameters) { String value = null;
if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject();
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; LocalDate value = null;
if (element.isJsonObject()) { if (element.isJsonObject()) {
JsonObject object = element.getAsJsonObject(); 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) if (object.has(parameterName)
&& object.get(parameterName).isJsonPrimitive()) { && object.get(parameterName).isJsonPrimitive()) {
modifiedParameters.add(parameterName);
JsonPrimitive primitive = object.get(parameterName) parametersPassedInCommand.add(parameterName);
.getAsJsonPrimitive();
final JsonPrimitive primitive = object.get(parameterName).getAsJsonPrimitive();
final String valueAsString = primitive.getAsString(); final String valueAsString = primitive.getAsString();
if (StringUtils.isNotBlank(valueAsString)) { if (StringUtils.isNotBlank(valueAsString)) {
value = convertFrom(valueAsString, parameterName, value = convertFrom(valueAsString, parameterName, dateFormat, clientApplicationLocale);
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) { private Locale localeFromString(final String localeAsString) {
if (StringUtils.isBlank(localeAsString)) { if (StringUtils.isBlank(localeAsString)) {
@@ -3,7 +3,8 @@ package org.mifosng.platform.charge.domain;
public enum ChargeTimeType { public enum ChargeTimeType {
INVALID(0, "chargeTimeType.invalid"), INVALID(0, "chargeTimeType.invalid"),
DISBURSEMENT(1, "chargeTimeType.disbursement"); DISBURSEMENT(1, "chargeTimeType.disbursement"),
SPECIFIED_DUE_DATE(2, "chargeTimeType.specifiedDueDate");
private final Integer value; private final Integer value;
private final String code; private final String code;
@@ -27,6 +28,9 @@ public enum ChargeTimeType {
case 1: case 1:
chargeTimeType = DISBURSEMENT; chargeTimeType = DISBURSEMENT;
break; break;
case 2:
chargeTimeType = SPECIFIED_DUE_DATE;
break;
default: default:
chargeTimeType = INVALID; chargeTimeType = INVALID;
break; break;
@@ -12,11 +12,11 @@ public class ChargeCommandValidator {
private final ChargeCommand command; private final ChargeCommand command;
public ChargeCommandValidator(ChargeCommand command) { public ChargeCommandValidator(final ChargeCommand command) {
this.command = command; this.command = command;
} }
public void validateForCreate(){ public void validateForCreate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge"); 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("amount").value(command.getAmount()).notNull().positiveAmount();
baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).notBlank(); baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).notBlank();
baseDataValidator.reset().parameter("chargeAppliesTo").value(command.getChargeAppliesTo()).notNull().inMinMaxRange(1, 1); 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); baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).notNull().inMinMaxRange(1, 4);
if (!dataValidationErrors.isEmpty()) { if (!dataValidationErrors.isEmpty()) {
@@ -33,7 +33,7 @@ public class ChargeCommandValidator {
} }
} }
public void validateForUpdate(){ public void validateForUpdate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge"); 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("amount").value(command.getAmount()).ignoreIfNull().positiveAmount();
baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).ignoreIfNull().notBlank(); baseDataValidator.reset().parameter("currencyCode").value(command.getCurrencyCode()).ignoreIfNull().notBlank();
baseDataValidator.reset().parameter("chargeAppliesTo").value(command.getChargeAppliesTo()).ignoreIfNull().notNull().inMinMaxRange(1, 1); 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); baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).ignoreIfNull().notNull().inMinMaxRange(1, 4);
if (!dataValidationErrors.isEmpty()) { if (!dataValidationErrors.isEmpty()) {
@@ -33,6 +33,6 @@ public class ChargeDropdownReadPlatformServiceImpl implements ChargeDropdownRead
@Override @Override
public List<EnumOptionData> retrieveCollectionTimeTypes() { 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)); return chargeTimeType(ChargeTimeType.fromInt(id));
} }
public static EnumOptionData chargeTimeType(ChargeTimeType type){ public static EnumOptionData chargeTimeType(final ChargeTimeType type){
EnumOptionData optionData = null; EnumOptionData optionData = null;
switch (type){ switch (type){
case DISBURSEMENT: case DISBURSEMENT:
optionData = new EnumOptionData(ChargeTimeType.DISBURSEMENT.getValue().longValue(), ChargeTimeType.DISBURSEMENT.getCode(), "Disbursement"); optionData = new EnumOptionData(ChargeTimeType.DISBURSEMENT.getValue().longValue(), ChargeTimeType.DISBURSEMENT.getCode(), "Disbursement");
break; break;
case SPECIFIED_DUE_DATE:
optionData = new EnumOptionData(ChargeTimeType.SPECIFIED_DUE_DATE.getValue().longValue(), ChargeTimeType.SPECIFIED_DUE_DATE.getCode(), "Specified due date");
break;
default: default:
optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid"); optionData = new EnumOptionData(ChargeTimeType.INVALID.getValue().longValue(), ChargeTimeType.INVALID.getCode(), "Invalid");
break; break;
@@ -28,7 +31,7 @@ public class ChargeEnumerations {
return chargeAppliesTo(ChargeAppliesTo.fromInt(id)); return chargeAppliesTo(ChargeAppliesTo.fromInt(id));
} }
public static EnumOptionData chargeAppliesTo(ChargeAppliesTo type){ public static EnumOptionData chargeAppliesTo(final ChargeAppliesTo type){
EnumOptionData optionData = null; EnumOptionData optionData = null;
switch (type){ switch (type){
case LOAN: case LOAN:
@@ -45,7 +48,7 @@ public class ChargeEnumerations {
return chargeCalculationType(ChargeCalculationType.fromInt(id)); return chargeCalculationType(ChargeCalculationType.fromInt(id));
} }
public static EnumOptionData chargeCalculationType(ChargeCalculationType type){ public static EnumOptionData chargeCalculationType(final ChargeCalculationType type){
EnumOptionData optionData = null; EnumOptionData optionData = null;
switch (type){ switch (type){
case FLAT: case FLAT:
@@ -6,6 +6,7 @@ import java.sql.SQLException;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import org.joda.time.LocalDate;
import org.mifosng.platform.api.data.ChargeData; import org.mifosng.platform.api.data.ChargeData;
import org.mifosng.platform.api.data.CurrencyData; import org.mifosng.platform.api.data.CurrencyData;
import org.mifosng.platform.api.data.EnumOptionData; 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.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.calculation_percentage as percentageOf, lc.calculation_on_amount as amountPercentageAppliedTo, " +
"lc.charge_time_enum as chargeTime, " + "lc.charge_time_enum as chargeTime, " +
"lc.due_for_collection_as_of_date as dueAsOfDate, " +
"lc.charge_calculation_enum as chargeCalculation, " + "lc.charge_calculation_enum as chargeCalculation, " +
"c.currency_code as currencyCode, oc.name as currencyName, " + "c.currency_code as currencyCode, oc.name as currencyName, " +
"oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " + "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 int chargeTime = rs.getInt("chargeTime");
final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime); final EnumOptionData chargeTimeType = ChargeEnumerations.chargeTimeType(chargeTime);
final LocalDate dueAsOfDate = JdbcSupport.getLocalDate(rs, "dueAsOfDate");
final int chargeCalculation = rs.getInt("chargeCalculation"); final int chargeCalculation = rs.getInt("chargeCalculation");
final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(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; private final ChargeRepository chargeRepository;
@Autowired @Autowired
public ChargeWritePlatformServiceJpaRepositoryImpl(PlatformSecurityContext context, ChargeRepository chargeRepository) { public ChargeWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final ChargeRepository chargeRepository) {
this.context = context; this.context = context;
this.chargeRepository = chargeRepository; this.chargeRepository = chargeRepository;
} }
@@ -31,21 +31,21 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
try { try {
this.context.authenticatedUser(); this.context.authenticatedUser();
ChargeCommandValidator validator = new ChargeCommandValidator(command); final ChargeCommandValidator validator = new ChargeCommandValidator(command);
validator.validateForCreate(); validator.validateForCreate();
ChargeAppliesTo chargeAppliesTo = ChargeAppliesTo.fromInt(command.getChargeAppliesTo()); final ChargeAppliesTo chargeAppliesTo = ChargeAppliesTo.fromInt(command.getChargeAppliesTo());
ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.getChargeTimeType()); final ChargeTimeType chargeTimeType = ChargeTimeType.fromInt(command.getChargeTimeType());
ChargeCalculationType chargeCalculationType = ChargeCalculationType.fromInt(command.getChargeCalculationType()); 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()); chargeAppliesTo, chargeTimeType, chargeCalculationType, command.isActive());
this.chargeRepository.saveAndFlush(charge); this.chargeRepository.saveAndFlush(charge);
return charge.getId(); return charge.getId();
} catch (DataIntegrityViolationException dve) { } catch (DataIntegrityViolationException dve) {
handleFundDataIntegrityIssues(command, dve); handleDataIntegrityIssues(command, dve);
return Long.valueOf(-1); return Long.valueOf(-1);
} }
} }
@@ -61,7 +61,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
validator.validateForUpdate(); validator.validateForUpdate();
final Long chargeId = command.getId(); final Long chargeId = command.getId();
Charge chargeForUpdate = this.chargeRepository.findOne(chargeId); final Charge chargeForUpdate = this.chargeRepository.findOne(chargeId);
if (chargeForUpdate == null){ if (chargeForUpdate == null){
throw new ChargeNotFoundException(chargeId); throw new ChargeNotFoundException(chargeId);
@@ -72,7 +72,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
return chargeForUpdate.getId(); return chargeForUpdate.getId();
} catch (DataIntegrityViolationException dve) { } catch (DataIntegrityViolationException dve) {
handleFundDataIntegrityIssues(command, dve); handleDataIntegrityIssues(command, dve);
return Long.valueOf(-1); return Long.valueOf(-1);
} }
} }
@@ -83,7 +83,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP
this.context.authenticatedUser(); this.context.authenticatedUser();
Charge chargeForDelete = this.chargeRepository.findOne(chargeId); final Charge chargeForDelete = this.chargeRepository.findOne(chargeId);
if (chargeForDelete == null || chargeForDelete.isDeleted()){ if (chargeForDelete == null || chargeForDelete.isDeleted()){
throw new ChargeNotFoundException(chargeId); 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. * 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(); Throwable realCause = dve.getMostSpecificCause();
if (realCause.getMessage().contains("name")) { 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) { 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) { public static Money zero(final MonetaryCurrency currency) {
@@ -56,14 +56,20 @@ public class Money implements Comparable<Money>, Serializable {
private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount) { private Money(final String currencyCode, final int digitsAfterDecimal, final BigDecimal amount) {
this.currencyCode = currencyCode; this.currencyCode = currencyCode;
this.currencyDigitsAfterDecimal = digitsAfterDecimal; this.currencyDigitsAfterDecimal = digitsAfterDecimal;
if (amount != null) {
BigDecimal amountStripped = amount.stripTrailingZeros(); final BigDecimal amountZeroed = defaultToZeroIfNull(amount);
this.amount = amountStripped.setScale(this.currencyDigitsAfterDecimal, RoundingMode.HALF_EVEN); BigDecimal amountStripped = amountZeroed.stripTrailingZeros();
} else { this.amount = amountStripped.setScale(this.currencyDigitsAfterDecimal, RoundingMode.HALF_EVEN);
this.amount = BigDecimal.ZERO;
}
} }
private static BigDecimal defaultToZeroIfNull(final BigDecimal value) {
BigDecimal result = BigDecimal.ZERO;
if (value != null) {
result = value;
}
return result;
}
public Money copy() { public Money copy() {
return new Money(this.currencyCode, this.currencyDigitsAfterDecimal, this.amount.stripTrailingZeros()); 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; package org.mifosng.platform.infrastructure;
import java.math.BigDecimal;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Timestamp; import java.sql.Timestamp;
@@ -36,7 +37,20 @@ public class JdbcSupport {
return (Long) JdbcUtils.getResultSetValue(rs, rs.findColumn(columnName), Long.class); 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); 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); this.charges.add(loanCharge);
updateTotalChargesDueAtDisbursement(); 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) { public void removeLoanCharge(final LoanCharge loanCharge) {
@@ -270,6 +274,10 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
if (removed) { if (removed) {
updateTotalChargesDueAtDisbursement(); 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) { public void updateLoanCharge(final LoanCharge loanCharge, final LoanChargeCommand command) {
@@ -373,7 +381,8 @@ public class Loan extends AbstractAuditableCustom<AppUser, Long> {
this, scheduledLoanInstallment.periodNumber(), this, scheduledLoanInstallment.periodNumber(),
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.periodDueDate(),
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.interestDue()); scheduledLoanInstallment.interestDue(),
scheduledLoanInstallment.chargesDue());
this.addRepaymentScheduleInstallment(installment); this.addRepaymentScheduleInstallment(installment);
} }
} }
@@ -3,6 +3,7 @@ package org.mifosng.platform.loan.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@@ -11,11 +12,15 @@ import javax.persistence.Entity;
import javax.persistence.JoinColumn; import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.Table; 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.api.commands.LoanChargeCommand;
import org.mifosng.platform.charge.domain.Charge; import org.mifosng.platform.charge.domain.Charge;
import org.mifosng.platform.charge.domain.ChargeCalculationType; import org.mifosng.platform.charge.domain.ChargeCalculationType;
import org.mifosng.platform.charge.domain.ChargeTimeType; import org.mifosng.platform.charge.domain.ChargeTimeType;
import org.mifosng.platform.exceptions.LoanChargeWithoutMandatoryFieldException;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity @Entity
@@ -33,6 +38,10 @@ public class LoanCharge extends AbstractPersistable<Long> {
@Column(name = "charge_time_enum", nullable = false) @Column(name = "charge_time_enum", nullable = false)
private Integer chargeTime; private Integer chargeTime;
@Temporal(TemporalType.DATE)
@Column(name = "due_for_collection_as_of_date")
private Date dueForCollectionAsOfDate;
@Column(name = "charge_calculation_enum") @Column(name = "charge_calculation_enum")
private Integer chargeCalculation; private Integer chargeCalculation;
@@ -80,13 +89,25 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.loan = null; this.loan = null;
this.charge = chargeDefinition; this.charge = chargeDefinition;
if (command.isChargeTimeTypeChanged()){ if (command.isChargeTimeTypeChanged()) {
this.chargeTime = command.getChargeTimeType(); this.chargeTime = command.getChargeTimeType();
} else { } else {
this.chargeTime = chargeDefinition.getChargeTime(); this.chargeTime = chargeDefinition.getChargeTime();
} }
if (command.isChargeCalculationTypeChanged()){ 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(); this.chargeCalculation = command.getChargeCalculationType();
} else { } else {
this.chargeCalculation = chargeDefinition.getChargeCalculation(); this.chargeCalculation = chargeDefinition.getChargeCalculation();
@@ -111,6 +132,20 @@ public class LoanCharge extends AbstractPersistable<Long> {
this.chargeTime = chargeDefinition.getChargeTime(); 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(); this.chargeCalculation = command.getChargeCalculationType();
} else { } else {
@@ -226,54 +261,24 @@ public class LoanCharge extends AbstractPersistable<Long> {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT); 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) { private boolean isGreaterThanZero(final BigDecimal value) {
return value.compareTo(BigDecimal.ZERO) == 1; return value.compareTo(BigDecimal.ZERO) == 1;
} }
public LoanChargeCommand toData() { public LoanChargeCommand toCommand() {
Set<String> modifiedParameters = new HashSet<String>();
final LocalDate specifiedDueDate = getDueForCollectionAsOfLocalDate();
final Set<String> modifiedParameters = new HashSet<String>();
return new LoanChargeCommand(modifiedParameters, this.getId(), this.loan.getId(), this.charge.getId(), 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() { private boolean determineIfFullyPaid() {
@@ -326,4 +331,17 @@ public class LoanCharge extends AbstractPersistable<Long> {
public boolean hasLoanIdentifiedBy(final Long loanId) { public boolean hasLoanIdentifiedBy(final Long loanId) {
return this.loan.hasIdentifyOf(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; private final Integer installmentNumber;
@Column(name = "principal_amount", scale=6, precision=19) @Column(name = "principal_amount", scale=6, precision=19)
private BigDecimal principal = BigDecimal.ZERO; private BigDecimal principal;
@Column(name = "interest_amount", scale=6, precision=19) @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) @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) @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) @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) @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) @Column(name = "interest_writtenoff_derived", scale=6, precision=19)
private BigDecimal interestWrittenOff = BigDecimal.ZERO; private BigDecimal interestWrittenOff;
@Column(name="completed_derived") @Column(name="completed_derived")
private boolean completed; private boolean completed;
@@ -67,19 +91,31 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.completed = false; this.completed = false;
} }
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, public LoanRepaymentScheduleInstallment(
final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest) { final Loan loan,
final Integer installmentNumber,
final LocalDate dueDate,
final BigDecimal principal,
final BigDecimal interest,
final BigDecimal feeCharges) {
this.loan = loan; this.loan = loan;
this.installmentNumber = installmentNumber; this.installmentNumber = installmentNumber;
this.dueDate = dueDate.toDateMidnight().toDate(); this.dueDate = dueDate.toDateMidnight().toDate();
this.principal = principal; this.principal = defaultToNullIfZero(principal);
this.principalCompleted = BigDecimal.ZERO; this.interest = defaultToNullIfZero(interest);
this.interest = interest; this.feeCharges = defaultToNullIfZero(feeCharges);
this.interestCompleted = BigDecimal.ZERO;
this.completed = false; 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; return this.loan;
} }
@@ -129,6 +165,27 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return getInterest(currency).minus(interestAccountedFor); 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) { public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterestOutstanding(currency).isGreaterThanZero(); return getInterestOutstanding(currency).isGreaterThanZero();
} }
@@ -247,4 +304,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public boolean isOverdueOn(final LocalDate transactionDate) { public boolean isOverdueOn(final LocalDate transactionDate) {
return this.getDueDate().isBefore(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; package org.mifosng.platform.loan.domain;
import java.util.List; 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.MonetaryCurrency;
import org.mifosng.platform.currency.domain.Money; import org.mifosng.platform.currency.domain.Money;
@@ -24,4 +26,44 @@ public class LoanScheduleWrapper {
return cumulativeValue; 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()) { if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); 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 { public interface CalculationPlatformService {
LoanScheduleData calculateLoanScheduleNew(CalculateLoanScheduleCommand command); LoanScheduleData calculateLoanSchedule(CalculateLoanScheduleCommand command);
} }
@@ -40,7 +40,7 @@ public class CalculationPlatformServiceImpl implements CalculationPlatformServic
} }
@Override @Override
public LoanScheduleData calculateLoanScheduleNew(final CalculateLoanScheduleCommand command) { public LoanScheduleData calculateLoanSchedule(final CalculateLoanScheduleCommand command) {
context.authenticatedUser(); context.authenticatedUser();
@@ -84,13 +84,15 @@ public class LoanAssembler {
} }
Client client = null; Client client = null;
Group group = null;
if (command.getClientId() != null) { if (command.getClientId() != null) {
client = this.clientRepository.findOne(command.getClientId()); client = this.clientRepository.findOne(command.getClientId());
if (client == null || client.isDeleted()) { if (client == null || client.isDeleted()) {
throw new ClientNotFoundException(command.getClientId()); throw new ClientNotFoundException(command.getClientId());
} }
} else { }
Group group = null;
if (command.getGroupId() != null) {
group = this.groupRepository.findOne(command.getGroupId()); group = this.groupRepository.findOne(command.getGroupId());
if (group == null || group.isDeleted()) { if (group == null || group.isDeleted()) {
throw new GroupNotFoundException(command.getGroupId()); throw new GroupNotFoundException(command.getGroupId());
@@ -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) // 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()); final Set<LoanCharge> loanCharges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanProduct.getCharges(), command.getPrincipal());
Loan loan; Loan loan = null;
if (client != null) { if (group != null) {
loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges);
} else {
loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, group, loanRepaymentScheduleDetail, loanCharges); loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, group, loanRepaymentScheduleDetail, loanCharges);
loan.setExternalId(command.getExternalId());
} }
loan.setExternalId(command.getExternalId()); if (client != null) {
loan = Loan.createNew(fund,loanOfficer, loanTransactionProcessingStrategy, loanProduct, client, loanRepaymentScheduleDetail, loanCharges);
loan.setExternalId(command.getExternalId());
}
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(command.toCalculateLoanScheduleCommand()); if (loan == null) {
// FIXME - kw - put in appropriate exception here.
throw new RuntimeException();
}
final LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(command.toCalculateLoanScheduleCommand());
for (LoanSchedulePeriodData scheduledLoanInstallment : loanSchedule.getPeriods()) { for (LoanSchedulePeriodData scheduledLoanInstallment : loanSchedule.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) { if (scheduledLoanInstallment.isRepaymentPeriod()) {
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(
loan, scheduledLoanInstallment.periodNumber(), loan, scheduledLoanInstallment.periodNumber(),
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.periodDueDate(),
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.interestDue()); scheduledLoanInstallment.interestDue(),
scheduledLoanInstallment.chargesDue());
loan.addRepaymentScheduleInstallment(installment); loan.addRepaymentScheduleInstallment(installment);
} }
} }
@@ -1,6 +1,5 @@
package org.mifosng.platform.loan.service; package org.mifosng.platform.loan.service;
import org.mifosng.platform.DataValidatorBuilder; import org.mifosng.platform.DataValidatorBuilder;
import org.mifosng.platform.api.commands.LoanChargeCommand; import org.mifosng.platform.api.commands.LoanChargeCommand;
import org.mifosng.platform.api.data.ApiParameterError; import org.mifosng.platform.api.data.ApiParameterError;
@@ -13,32 +12,38 @@ public class LoanChargeCommandValidator {
private final LoanChargeCommand command; private final LoanChargeCommand command;
public LoanChargeCommandValidator(LoanChargeCommand command) { public LoanChargeCommandValidator(final LoanChargeCommand command) {
this.command = command; this.command = command;
} }
public void validateForCreate(){ public void validateForCreate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge"); DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
baseDataValidator.reset().parameter("chargeId").value(command.getChargeId()).notNull().longGreaterThanZero(); baseDataValidator.reset().parameter("chargeId").value(command.getChargeId()).notNull().longGreaterThanZero();
baseDataValidator.reset().parameter("amount").value(command.getAmount()).ignoreIfNull().positiveAmount(); baseDataValidator.reset().parameter("amount").value(command.getAmount()).notNull().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 (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()) { if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors); throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.", dataValidationErrors);
} }
} }
public void validateForUpdate(){ public void validateForUpdate() {
List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>(); List<ApiParameterError> dataValidationErrors = new ArrayList<ApiParameterError>();
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge"); DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("charge");
baseDataValidator.reset().parameter("amount").value(command.getAmount()).ignoreIfNull().positiveAmount(); 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); baseDataValidator.reset().parameter("chargeCalculationType").value(command.getChargeCalculationType()).ignoreIfNull().inMinMaxRange(1, 4);
if (!dataValidationErrors.isEmpty()) { if (!dataValidationErrors.isEmpty()) {
@@ -507,18 +507,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private static final class LoanScheduleMapper implements RowMapper<LoanSchedulePeriodData> { private static final class LoanScheduleMapper implements RowMapper<LoanSchedulePeriodData> {
private LocalDate lastDueDate; private LocalDate lastDueDate;
private BigDecimal outstandingLoanBalance; private BigDecimal outstandingLoanPrincipalBalance;
public LoanScheduleMapper(final DisbursementData disbursement) { public LoanScheduleMapper(final DisbursementData disbursement) {
this.lastDueDate = disbursement.disbursementDate(); this.lastDueDate = disbursement.disbursementDate();
this.outstandingLoanBalance = disbursement.amount(); this.outstandingLoanPrincipalBalance = disbursement.amount();
} }
public String loanScheduleSchema() { public String loanScheduleSchema() {
return " ls.loan_id as loanId, ls.installment as period, ls.duedate as dueDate, " 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.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 " + " from m_loan l "
+ " join m_loan_repayment_schedule ls on ls.loan_id = l.id "; + " 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 Long loanId = rs.getLong("loanId");
final Integer period = JdbcSupport.getInteger(rs, "period"); final Integer period = JdbcSupport.getInteger(rs, "period");
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate"); final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
final BigDecimal principalDue = rs.getBigDecimal("principalDue"); final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDue");
final BigDecimal principalPaid = rs.getBigDecimal("principalPaid"); final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
BigDecimal principalWrittenOff = rs.getBigDecimal("principalWrittenOff"); final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
if (principalWrittenOff == null) {
principalWrittenOff = BigDecimal.ZERO;
}
// TODO - KW - rather than calculate this here should we put calculation on derived column on table like other columns? // 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 principalOutstanding = principalDue.subtract(principalPaid).subtract(principalWrittenOff);
final BigDecimal interestDue = rs.getBigDecimal("interestDue"); final BigDecimal interestDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue");
final BigDecimal interestPaid = rs.getBigDecimal("interestPaid"); final BigDecimal interestPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestPaid");
BigDecimal interestWaived = rs.getBigDecimal("interestWaived"); final BigDecimal interestWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWaived");
if (interestWaived == null) { final BigDecimal interestWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestWrittenOff");
interestWaived = BigDecimal.ZERO;
}
BigDecimal interestWrittenOff = rs.getBigDecimal("interestWrittenOff");
if (interestWrittenOff == null) {
interestWrittenOff = BigDecimal.ZERO;
}
// TODO - KW - rather than calculate this here should we put calculation on derived column on table like other columns? // 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 interestOutstanding = interestDue.subtract(interestPaid).subtract(interestWaived).subtract(interestWrittenOff);
final BigDecimal totalDueForPeriod = principalDue.add(interestDue); final BigDecimal feeChargesDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesDue");
final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid); final BigDecimal feeChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesPaid");
final BigDecimal totalWaivedForPeriod = interestWaived; final BigDecimal feeChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWaived");
final BigDecimal totalWrittenOffForPeriod = principalWrittenOff.add(interestWrittenOff); final BigDecimal feeChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesWrittenOff");
final BigDecimal totalOutstandingForPeriod = principalOutstanding.add(interestOutstanding);
final BigDecimal feeChargesOutstanding = feeChargesDue.subtract(feeChargesPaid).subtract(feeChargesWaived).subtract(feeChargesWrittenOff);
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 LocalDate fromDate = this.lastDueDate;
final BigDecimal outstandingPrincipleBalanceOfLoan = outstandingLoanBalance.subtract(principalDue); final BigDecimal outstandingPrincipleBalanceOfLoan = outstandingLoanPrincipalBalance.subtract(principalDue);
// update based on current period values // update based on current period values
this.lastDueDate = dueDate; this.lastDueDate = dueDate;
outstandingLoanBalance = outstandingLoanBalance.subtract(principalDue); outstandingLoanPrincipalBalance = outstandingLoanPrincipalBalance.subtract(principalDue);
return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate,
principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipleBalanceOfLoan, interestDue, principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipleBalanceOfLoan,
interestPaid, interestWaived, interestWrittenOff, interestOutstanding, totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, interestDue, interestPaid, interestWaived, interestWrittenOff, interestOutstanding,
totalOutstandingForPeriod); 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 LoanTransactionProcessingStrategy strategy = this.loanAssembler.findStrategyByIdIfProvided(command.getTransactionProcessingStrategyId());
final Set<LoanCharge> charges = this.loanChargeAssembler.assembleFrom(command.getCharges(), loanProduct.getCharges(), loan.getPrincpal().getAmount()); 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()); loan.modifyLoanApplication(command, client, loanProduct, fund, strategy, loanSchedule, charges, loanOfficer, defaultLoanLifecycleStateMachine());
this.loanRepository.save(loan); this.loanRepository.save(loan);
@@ -361,7 +361,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Set<LoanCharge> loanCharges = loan.getCharges(); Set<LoanCharge> loanCharges = loan.getCharges();
List<LoanChargeCommand> commands = new ArrayList<LoanChargeCommand>(); List<LoanChargeCommand> commands = new ArrayList<LoanChargeCommand>();
for (LoanCharge loanCharge : loanCharges) { for (LoanCharge loanCharge : loanCharges) {
commands.add(loanCharge.toData()); commands.add(loanCharge.toCommand());
} }
LoanChargeCommand[] loanChargeCommands = commands.toArray(new LoanChargeCommand[commands.size()]); LoanChargeCommand[] loanChargeCommands = commands.toArray(new LoanChargeCommand[commands.size()]);
@@ -374,7 +374,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanTermFrequency, loanTermFrequencyType, loanTermFrequency, loanTermFrequencyType,
actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate, loanChargeCommands); actualDisbursementDate, repaymentsStartingFromDate, interestCalculatedFromDate, loanChargeCommands);
LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanScheduleNew(calculateCommand); LoanScheduleData loanSchedule = this.calculationPlatformService.calculateLoanSchedule(calculateCommand);
List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule = new ArrayList<LoanRepaymentScheduleInstallment>(); List<LoanRepaymentScheduleInstallment> modifiedLoanRepaymentSchedule = new ArrayList<LoanRepaymentScheduleInstallment>();
@@ -384,7 +384,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan, scheduledLoanPeriod.periodNumber(), loan, scheduledLoanPeriod.periodNumber(),
scheduledLoanPeriod.periodDueDate(), scheduledLoanPeriod.periodDueDate(),
scheduledLoanPeriod.principalDue(), scheduledLoanPeriod.principalDue(),
scheduledLoanPeriod.interestDue()); scheduledLoanPeriod.interestDue(),
scheduledLoanPeriod.chargesDue());
modifiedLoanRepaymentSchedule.add(installment); modifiedLoanRepaymentSchedule.add(installment);
} }
@@ -16,7 +16,6 @@ public interface AmortizationLoanScheduleGenerator {
ApplicationCurrency applicationCurrency, ApplicationCurrency applicationCurrency,
LoanProductRelatedDetail loanScheduleInfo, LoanProductRelatedDetail loanScheduleInfo,
LocalDate disbursementDate, LocalDate disbursementDate,
LocalDate firstRepaymentDate,
LocalDate interestCalculatedFrom, LocalDate interestCalculatedFrom,
BigDecimal periodInterestRateForRepaymentPeriod, BigDecimal periodInterestRateForRepaymentPeriod,
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
@@ -60,7 +60,6 @@ public class DecliningBalanceMethodLoanScheduleGenerator implements LoanSchedule
return generator.generate(applicationCurrency, return generator.generate(applicationCurrency,
loanScheduleInfo, loanScheduleInfo,
disbursementDate, disbursementDate,
firstRepaymentDate,
interestCalculatedFrom, interestCalculatedFrom,
periodInterestRateForRepaymentPeriod, periodInterestRateForRepaymentPeriod,
idealDisbursementDateBasedOnFirstRepaymentDate, idealDisbursementDateBasedOnFirstRepaymentDate,
@@ -31,7 +31,6 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
final ApplicationCurrency currency, final ApplicationCurrency currency,
final LoanProductRelatedDetail loanScheduleInfo, final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate, final LocalDate disbursementDate,
final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom, final LocalDate interestCalculatedFrom,
final BigDecimal periodInterestRateForRepaymentPeriod, final BigDecimal periodInterestRateForRepaymentPeriod,
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
@@ -60,7 +59,6 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO; BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
for (LoanCharge loanCharge : loanCharges) { for (LoanCharge loanCharge : loanCharges) {
// FIXME - KW - right now only charges at disbursement are supported.
if (loanCharge.isDueAtDisbursement()) { if (loanCharge.isDueAtDisbursement()) {
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount()); chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
} }
@@ -119,15 +117,19 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
} }
} }
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment);
outstandingBalance = outstandingBalance.minus(principalForInstallment); 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, LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate,
scheduledDueDate, scheduledDueDate,
principalForInstallment.getAmount(), principalForInstallment.getAmount(),
outstandingBalance.getAmount(), outstandingBalance.getAmount(),
interestForInstallment.getAmount(), totalInstallmentDue.getAmount()); interestForInstallment.getAmount(),
chargesForInstallment.getAmount(),
totalInstallmentDue.getAmount());
periods.add(installment); periods.add(installment);
@@ -153,4 +155,21 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort
return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue, return new LoanScheduleData(currencyData, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue,
cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment); 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 ApplicationCurrency currency,
final LoanProductRelatedDetail loanScheduleInfo, final LoanProductRelatedDetail loanScheduleInfo,
final LocalDate disbursementDate, final LocalDate disbursementDate,
final LocalDate firstRepaymentDate,
final LocalDate interestCalculatedFrom, final LocalDate interestCalculatedFrom,
final BigDecimal periodInterestRateForRepaymentPeriod, final BigDecimal periodInterestRateForRepaymentPeriod,
final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate, final LocalDate idealDisbursementDateBasedOnFirstRepaymentDate,
@@ -50,7 +49,6 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO; BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
for (LoanCharge loanCharge : loanCharges) { for (LoanCharge loanCharge : loanCharges) {
// FIXME - KW - right now only charges at disbursement are supported.
if (loanCharge.isDueAtDisbursement()) { if (loanCharge.isDueAtDisbursement()) {
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount()); chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
} }
@@ -100,15 +98,21 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza
} }
} }
Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment);
outstandingBalance = outstandingBalance.minus(principalForInstallment); 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, LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate,
scheduledDueDate, scheduledDueDate,
principalForInstallment.getAmount(), principalForInstallment.getAmount(),
outstandingBalance.getAmount(), outstandingBalance.getAmount(),
interestForInstallment.getAmount(), totalInstallmentDue.getAmount()); interestForInstallment.getAmount(),
chargesForInstallment.getAmount(),
totalInstallmentDue.getAmount());
periods.add(installment); periods.add(installment);
@@ -65,7 +65,6 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO; BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
for (LoanCharge loanCharge : loanCharges) { for (LoanCharge loanCharge : loanCharges) {
// FIXME - KW - right now only charges at disbursement are supported.
if (loanCharge.isDueAtDisbursement()) { if (loanCharge.isDueAtDisbursement()) {
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount()); chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
} }
@@ -108,14 +107,20 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator {
} }
} }
Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment);
outstandingBalance = outstandingBalance.minus(principalPerInstallment); 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, LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate,
scheduledDueDate, scheduledDueDate,
principalPerInstallment.getAmount(), principalPerInstallment.getAmount(),
outstandingBalance.getAmount(), outstandingBalance.getAmount(),
interestPerInstallment.getAmount(), totalInstallmentDue.getAmount()); interestPerInstallment.getAmount(),
chargesForInstallment.getAmount(),
totalInstallmentDue.getAmount());
periods.add(installment); periods.add(installment);
@@ -12,6 +12,7 @@ public class LoanRepaymentScheduleInstallmentBuilder {
private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build(); private MonetaryCurrency currencyDetail = new MonetaryCurrencyBuilder().build();
private Money principal = new MoneyBuilder().build(); private Money principal = new MoneyBuilder().build();
private Money interest = new MoneyBuilder().build(); private Money interest = new MoneyBuilder().build();
private Money feeCharges = new MoneyBuilder().build();
private boolean completed = false; private boolean completed = false;
public LoanRepaymentScheduleInstallmentBuilder(MonetaryCurrency currencyDetail) { public LoanRepaymentScheduleInstallmentBuilder(MonetaryCurrency currencyDetail) {
@@ -21,7 +22,7 @@ public class LoanRepaymentScheduleInstallmentBuilder {
} }
public LoanRepaymentScheduleInstallment build() { 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) { if (completed) {
installment.payPrincipalComponent(principal); installment.payPrincipalComponent(principal);
installment.payInterestComponent(interest); installment.payInterestComponent(interest);