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