From a3200a8bc79dc04063834893ad85d388fb0cba25 Mon Sep 17 00:00:00 2001 From: Keith Woodlock Date: Wed, 31 Oct 2012 11:08:54 +0000 Subject: [PATCH] MIFOSX-90: work in progress on charges (fees and penalties) for loans suitable for gk-maarg. Split out charges on repayment schedule into fee charges and penalty charges. work outstanding on integrating payment processing to auto pay off charges (or auto penalise). --- .../patch-0006-repayment-schedule-changes.sql | 8 ++ .../platform/api/ChargesApiResource.java | 32 ++--- .../platform/api/LoanProductsApiResource.java | 54 ++------ .../platform/api/LoanScheduleData.java | 59 ++++++--- .../platform/api/LoansApiResource.java | 114 ++++++----------- .../platform/api/commands/ChargeCommand.java | 34 ++++- .../mifosng/platform/api/data/ChargeData.java | 51 +++----- .../api/data/LoanBasicDetailsData.java | 4 +- .../platform/api/data/LoanChargeData.java | 20 ++- .../api/data/LoanSchedulePeriodData.java | 117 +++++++++++++---- .../ApiDataConversionServiceImpl.java | 27 ++-- .../GoogleGsonApiJsonSerializerService.java | 2 +- .../GoogleGsonSerializerHelper.java | 2 +- .../platform/charge/domain/Charge.java | 23 +++- .../service/ChargeCommandValidator.java | 4 + .../service/ChargeReadPlatformService.java | 2 +- .../ChargeReadPlatformServiceImpl.java | 30 +++-- ...WritePlatformServiceJpaRepositoryImpl.java | 2 +- .../mifosng/platform/loan/domain/Loan.java | 7 +- .../platform/loan/domain/LoanCharge.java | 15 ++- .../LoanRepaymentScheduleInstallment.java | 121 ++++++++++-------- .../loan/domain/LoanScheduleWrapper.java | 30 ++++- .../platform/loan/service/LoanAssembler.java | 7 +- .../service/LoanReadPlatformServiceImpl.java | 64 ++++++--- .../LoanSchedulePeriodDataWrapper.java | 36 +++++- ...WritePlatformServiceJpaRepositoryImpl.java | 9 +- ...entsAmortizationLoanScheduleGenerator.java | 31 ++++- ...ipalAmortizationLoanScheduleGenerator.java | 12 +- .../FlatMethodLoanScheduleGenerator.java | 12 +- ...anRepaymentScheduleInstallmentBuilder.java | 4 +- 30 files changed, 563 insertions(+), 370 deletions(-) create mode 100644 mifosng-db/patches/patch-0006-repayment-schedule-changes.sql diff --git a/mifosng-db/patches/patch-0006-repayment-schedule-changes.sql b/mifosng-db/patches/patch-0006-repayment-schedule-changes.sql new file mode 100644 index 000000000..9beaf01a6 --- /dev/null +++ b/mifosng-db/patches/patch-0006-repayment-schedule-changes.sql @@ -0,0 +1,8 @@ +alter table m_loan_repayment_schedule +add column `fromdate` date DEFAULT NULL after `loan_id`; + +alter table m_charge +add column `is_penalty` tinyint(1) NOT NULL DEFAULT '0' after `amount`; + +alter table m_loan_charge +add column `is_penalty` tinyint(1) NOT NULL DEFAULT '0' after `charge_id`; \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/ChargesApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/ChargesApiResource.java index 27271c03a..aae901fd2 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/ChargesApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/ChargesApiResource.java @@ -50,10 +50,10 @@ public class ChargesApiResource { @Produces({MediaType.APPLICATION_JSON}) public String retrieveAllCharges(@Context final UriInfo uriInfo){ - Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); + final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); + final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); - Collection charges = this.chargeReadPlatformService.retrieveAllCharges(); + final Collection charges = this.chargeReadPlatformService.retrieveAllCharges(); return this.apiJsonSerializerService.serializeChargeDataToJson(prettyPrint, responseParameters, charges); } @@ -63,16 +63,15 @@ public class ChargesApiResource { @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.APPLICATION_JSON}) public String retrieveCharge(@PathParam("chargeId") final Long chargeId, @Context final UriInfo uriInfo){ - Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); + + final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); - boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); + final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); + final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); ChargeData charge = this.chargeReadPlatformService.retrieveCharge(chargeId); - - if (template){ + if (template) { ChargeData templateData = this.chargeReadPlatformService.retrieveNewChargeDetails(); - charge = new ChargeData(charge, templateData); } @@ -84,11 +83,12 @@ public class ChargesApiResource { @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.APPLICATION_JSON}) public String retrieveNewChargeDetails(@Context final UriInfo uriInfo) { - Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); + + final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); + final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); - ChargeData chargeData = this.chargeReadPlatformService.retrieveNewChargeDetails(); + final ChargeData chargeData = this.chargeReadPlatformService.retrieveNewChargeDetails(); return this.apiJsonSerializerService.serializeChargeDataToJson(prettyPrint, responseParameters, chargeData); } @@ -98,9 +98,9 @@ public class ChargesApiResource { @Produces({MediaType.APPLICATION_JSON}) public Response createCharge(final String jsonRequestBody){ - ChargeCommand command = this.apiDataConversionService.convertJsonToChargeCommand(null, jsonRequestBody); + final ChargeCommand command = this.apiDataConversionService.convertJsonToChargeCommand(null, jsonRequestBody); - Long chargeId = this.chargeWritePlatformService.createCharge(command); + final Long chargeId = this.chargeWritePlatformService.createCharge(command); return Response.ok().entity(new EntityIdentifier(chargeId)).build(); } @@ -111,9 +111,9 @@ public class ChargesApiResource { @Produces({MediaType.APPLICATION_JSON}) public Response updateCharge(@PathParam("chargeId") final Long chargeId, final String jsonRequestBody){ - ChargeCommand command = this.apiDataConversionService.convertJsonToChargeCommand(chargeId, jsonRequestBody); + final ChargeCommand command = this.apiDataConversionService.convertJsonToChargeCommand(chargeId, jsonRequestBody); - Long entityId = this.chargeWritePlatformService.updateCharge(command); + final Long entityId = this.chargeWritePlatformService.updateCharge(command); return Response.ok().entity(new EntityIdentifier(entityId)).build(); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanProductsApiResource.java index f0328a657..74ade6d7a 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanProductsApiResource.java @@ -1,8 +1,6 @@ package org.mifosng.platform.api; -import java.util.Arrays; import java.util.Collection; -import java.util.HashSet; import java.util.List; import java.util.Set; @@ -66,19 +64,6 @@ public class LoanProductsApiResource { @Autowired private LoanDropdownReadPlatformService dropdownReadPlatformService; - - private final Set typicalResponseParameters = new HashSet( - Arrays.asList("id", "name", "description", - "fundId", "fundName", - "transactionProcessingStrategyId", "transactionProcessingStrategyName", - "principal", "inArrearsTolerance", - "numberOfRepayments", "repaymentEvery", - "interestRatePerPeriod", "annualInterestRate", - "repaymentFrequencyType", "interestRateFrequencyType", - "amortizationType", "interestType", "interestCalculationPeriodType", - "charges", - "createdOn", "lastModifedOn") - ); @POST @Consumes({MediaType.APPLICATION_JSON}) @@ -98,9 +83,6 @@ public class LoanProductsApiResource { public String retrieveAllLoanProducts(@Context final UriInfo uriInfo) { final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - if (responseParameters.isEmpty()) { - responseParameters.addAll(typicalResponseParameters); - } final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); final Collection products = this.loanProductReadPlatformService.retrieveAllLoanProducts(); @@ -115,13 +97,10 @@ public class LoanProductsApiResource { public String retrieveNewLoanProductDetails(@Context final UriInfo uriInfo) { final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - if (responseParameters.isEmpty()) { - responseParameters.addAll(typicalResponseParameters); - } final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveNewLoanProductDetails(); - loanProduct = handleTemplate(loanProduct, responseParameters); + loanProduct = handleTemplate(loanProduct); return this.apiJsonSerializerService.serializeLoanProductDataToJson(prettyPrint, responseParameters, loanProduct); } @@ -133,15 +112,12 @@ public class LoanProductsApiResource { public String retrieveLoanProductDetails(@PathParam("productId") final Long productId, @Context final UriInfo uriInfo) { final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - if (responseParameters.isEmpty()) { - responseParameters.addAll(typicalResponseParameters); - } final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); LoanProductData loanProduct = this.loanProductReadPlatformService.retrieveLoanProduct(productId); if (template) { - loanProduct = handleTemplate(loanProduct, responseParameters); + loanProduct = handleTemplate(loanProduct); } return this.apiJsonSerializerService.serializeLoanProductDataToJson(prettyPrint, responseParameters, loanProduct); @@ -160,24 +136,22 @@ public class LoanProductsApiResource { return apiJsonSerializerService.serializeEntityIdentifier(identifier); } - private LoanProductData handleTemplate(final LoanProductData productData, final Set responseParameters) { + private LoanProductData handleTemplate(final LoanProductData productData) { - responseParameters.addAll(Arrays.asList("currencyOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", - "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "transactionProcessingStrategyOptions", "chargeOptions")); - - Collection chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(); + final boolean feeChargesOnly = true; + Collection chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly); chargeOptions.removeAll(productData.charges()); - List currencyOptions = currencyReadPlatformService.retrieveAllowedCurrencies(); - List amortizationTypeOptions = dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); - List interestTypeOptions = dropdownReadPlatformService.retrieveLoanInterestTypeOptions(); - List interestCalculationPeriodTypeOptions = dropdownReadPlatformService.retrieveLoanInterestRateCalculatedInPeriodOptions(); - List loanTermFrequencyTypeOptions = dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions(); - List repaymentFrequencyTypeOptions = dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions(); - List interestRateFrequencyTypeOptions = dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); + final List currencyOptions = currencyReadPlatformService.retrieveAllowedCurrencies(); + final List amortizationTypeOptions = dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); + final List interestTypeOptions = dropdownReadPlatformService.retrieveLoanInterestTypeOptions(); + final List interestCalculationPeriodTypeOptions = dropdownReadPlatformService.retrieveLoanInterestRateCalculatedInPeriodOptions(); + final List loanTermFrequencyTypeOptions = dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions(); + final List repaymentFrequencyTypeOptions = dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions(); + final List interestRateFrequencyTypeOptions = dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); - Collection fundOptions = this.fundReadPlatformService.retrieveAllFunds(); - Collection transactionProcessingStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies(); + final Collection fundOptions = this.fundReadPlatformService.retrieveAllFunds(); + final Collection transactionProcessingStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies(); return new LoanProductData(productData, chargeOptions, currencyOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanScheduleData.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanScheduleData.java index be031a8b9..ac8648685 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanScheduleData.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/LoanScheduleData.java @@ -58,14 +58,21 @@ public class LoanScheduleData { private final BigDecimal cumulativeInterestOutstanding; /** - * The cumulative total of all charges applied on the loan to date. + * The cumulative total of all fee charges applied on the loan to date. */ @SuppressWarnings("unused") - private final BigDecimal cumulativeChargesToDate; + private final BigDecimal cumulativeFeeChargesToDate; @SuppressWarnings("unused") - private final BigDecimal cumulativeChargesPaid; + private final BigDecimal cumulativeFeeChargesPaid; @SuppressWarnings("unused") - private final BigDecimal cumulativeChargesOutstanding; + private final BigDecimal cumulativeFeeChargesOutstanding; + + @SuppressWarnings("unused") + private final BigDecimal cumulativePenaltyChargesToDate; + @SuppressWarnings("unused") + private final BigDecimal cumulativePenaltyChargesPaid; + @SuppressWarnings("unused") + private final BigDecimal cumulativePenaltyChargesOutstanding; /** * The cumulative total of all costs on the loan. @@ -135,9 +142,13 @@ public class LoanScheduleData { this.cumulativeInterestWrittenOff = null; this.cumulativeInterestOutstanding = null; - this.cumulativeChargesToDate = null; - this.cumulativeChargesPaid = null; - this.cumulativeChargesOutstanding = null; + this.cumulativeFeeChargesToDate = null; + this.cumulativeFeeChargesPaid = null; + this.cumulativeFeeChargesOutstanding = null; + + this.cumulativePenaltyChargesToDate = null; + this.cumulativePenaltyChargesPaid = null; + this.cumulativePenaltyChargesOutstanding = null; this.totalCostOfLoan = null; @@ -161,7 +172,7 @@ public class LoanScheduleData { final BigDecimal cumulativePrincipalDue, final BigDecimal cumulativePrincipalOutstanding, final BigDecimal cumulativeInterestExpected, - final BigDecimal cumulativeChargesToDate, + final BigDecimal cumulativeFeeChargesToDate, final BigDecimal totalExpectedRepayment) { this.currency = currency; this.periods = periods; @@ -179,12 +190,16 @@ public class LoanScheduleData { this.cumulativeInterestWrittenOff = null; this.cumulativeInterestOutstanding = null; - this.cumulativeChargesToDate = cumulativeChargesToDate; - this.cumulativeChargesPaid = null; - this.cumulativeChargesOutstanding = null; + this.cumulativeFeeChargesToDate = cumulativeFeeChargesToDate; + this.cumulativeFeeChargesPaid = null; + this.cumulativeFeeChargesOutstanding = null; - if (cumulativeChargesToDate != null) { - this.totalCostOfLoan = cumulativeInterestExpected.add(cumulativeChargesToDate); + this.cumulativePenaltyChargesToDate = null; + this.cumulativePenaltyChargesPaid = null; + this.cumulativePenaltyChargesOutstanding = null; + + if (cumulativeFeeChargesToDate != null) { + this.totalCostOfLoan = cumulativeInterestExpected.add(cumulativeFeeChargesToDate); } else if (cumulativeInterestExpected != null) { this.totalCostOfLoan = cumulativeInterestExpected; } else { @@ -214,9 +229,12 @@ public class LoanScheduleData { final BigDecimal cumulativeInterestWaived, final BigDecimal cumulativeInterestWrittenOff, final BigDecimal cumulativeInterestOutstanding, - final BigDecimal cumulativeChargesToDate, - final BigDecimal cumulativeChargesPaid, - final BigDecimal cumulativeChargesOutstanding, + final BigDecimal cumulativeFeeChargesToDate, + final BigDecimal cumulativeFeeChargesPaid, + final BigDecimal cumulativeFeeChargesOutstanding, + final BigDecimal cumulativePenaltyChargesToDate, + final BigDecimal cumulativePenaltyChargesPaid, + final BigDecimal cumulativePenaltyChargesOutstanding, final BigDecimal totalCostOfLoan, final BigDecimal totalExpectedRepayment, final BigDecimal totalPaidToDate, @@ -238,9 +256,12 @@ public class LoanScheduleData { this.cumulativeInterestWrittenOff = cumulativeInterestWrittenOff; this.cumulativeInterestWaived = cumulativeInterestWaived; this.cumulativeInterestOutstanding = cumulativeInterestOutstanding; - this.cumulativeChargesToDate = cumulativeChargesToDate; - this.cumulativeChargesPaid = cumulativeChargesPaid; - this.cumulativeChargesOutstanding = cumulativeChargesOutstanding; + this.cumulativeFeeChargesToDate = cumulativeFeeChargesToDate; + this.cumulativeFeeChargesPaid = cumulativeFeeChargesPaid; + this.cumulativeFeeChargesOutstanding = cumulativeFeeChargesOutstanding; + this.cumulativePenaltyChargesToDate = cumulativePenaltyChargesToDate; + this.cumulativePenaltyChargesPaid = cumulativePenaltyChargesPaid; + this.cumulativePenaltyChargesOutstanding = cumulativePenaltyChargesOutstanding; this.totalCostOfLoan = totalCostOfLoan; this.totalExpectedRepayment = totalExpectedRepayment; this.totalPaidToDate = totalPaidToDate; 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 7860ce903..e6f165a4f 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 @@ -1,6 +1,5 @@ package org.mifosng.platform.api; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; @@ -95,25 +94,6 @@ public class LoansApiResource { @Autowired private StaffReadPlatformService staffReadPlatformService; - private final static Set typicalResponseParameters = new HashSet( - Arrays.asList("id", "externalId", "clientId", "groupId", "clientName", "groupName", "fundId", "fundName", - "loanProductId", "loanProductName", "loanProductDescription", - "loanOfficerName", "loanOfficerId", - "currency", "principal", - "inArrearsTolerance", "numberOfRepayments", - "repaymentEvery", "interestRatePerPeriod", - "annualInterestRate", "repaymentFrequencyType", - "interestRateFrequencyType", "amortizationType", - "interestType", "interestCalculationPeriodType", - "submittedOnDate", "approvedOnDate", - "expectedDisbursementDate", "actualDisbursementDate", - "expectedFirstRepaymentOnDate", "interestChargedFromDate", - "closedOnDate", "expectedMaturityDate", - "status", - "lifeCycleStatusDate")); - - final Set typicalTransactionResponseParameters = new HashSet(Arrays.asList("id", "type", "date", "currency", "amount")); - @GET @Path("template") @Consumes({ MediaType.APPLICATION_JSON }) @@ -124,31 +104,24 @@ public class LoansApiResource { @QueryParam("productId") final Long productId, @Context final UriInfo uriInfo) { + final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); + final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); - Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - if (responseParameters.isEmpty()) { - responseParameters.addAll(typicalResponseParameters); - } - boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); - - responseParameters.addAll(Arrays.asList("productOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", - "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", - "chargeTemplate", "charges")); - // tempate related Collection productOptions = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(); - CollectionloanTermFrequencyTypeOptions = dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions(); - CollectionrepaymentFrequencyTypeOptions = dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions(); - CollectioninterestRateFrequencyTypeOptions = dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); + Collection loanTermFrequencyTypeOptions = dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions(); + Collection repaymentFrequencyTypeOptions = dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions(); + Collection interestRateFrequencyTypeOptions = dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); - CollectionamortizationTypeOptions = dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); - CollectioninterestTypeOptions = dropdownReadPlatformService.retrieveLoanInterestTypeOptions(); - CollectioninterestCalculationPeriodTypeOptions = dropdownReadPlatformService.retrieveLoanInterestRateCalculatedInPeriodOptions(); + Collection amortizationTypeOptions = dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); + Collection interestTypeOptions = dropdownReadPlatformService.retrieveLoanInterestTypeOptions(); + Collection interestCalculationPeriodTypeOptions = dropdownReadPlatformService.retrieveLoanInterestRateCalculatedInPeriodOptions(); Collection fundOptions = this.fundReadPlatformService.retrieveAllFunds(); Collection repaymentStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies(); - Collection chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(); + final boolean feeChargesOnly = false; + Collection chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly); ChargeData chargeTemplate = this.chargeReadPlatformService.retrieveLoanChargeTemplate(); LoanBasicDetailsData loanBasicDetails; @@ -183,11 +156,8 @@ public class LoansApiResource { @PathParam("loanId") final Long loanId, @Context final UriInfo uriInfo) { - Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - if (responseParameters.isEmpty()) { - responseParameters.addAll(typicalResponseParameters); - } - boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); + final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); + final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); final LoanBasicDetailsData loanBasicDetails = this.loanReadPlatformService.retrieveLoanAccountDetails(loanId); @@ -202,19 +172,17 @@ public class LoansApiResource { if (!associationParameters.isEmpty()) { if (associationParameters.contains("all")) { - responseParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "permissions", "convenienceData", "charges")); - } else { - responseParameters.addAll(associationParameters); + associationParameters.addAll(Arrays.asList("repaymentSchedule", "transactions", "permissions", "convenienceData", "charges")); } - if (responseParameters.contains("transactions")) { + if (associationParameters.contains("transactions")) { final Collection currentLoanRepayments = this.loanReadPlatformService.retrieveLoanTransactions(loanId); if (!CollectionUtils.isEmpty(currentLoanRepayments)) { loanRepayments = currentLoanRepayments; } } - if (responseParameters.contains("repaymentSchedule")) { + if (associationParameters.contains("repaymentSchedule")) { DisbursementData singleDisbursement = loanBasicDetails.toDisburementData(); repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(loanId, loanBasicDetails.getCurrency(), @@ -227,14 +195,14 @@ public class LoansApiResource { boolean isWaiveAllowed = totalOutstandingMoney.isGreaterThanZero() && (tolerance.isGreaterThan(totalOutstandingMoney) || tolerance.isEqualTo(totalOutstandingMoney)); - if (responseParameters.contains("permissions")) { + if (associationParameters.contains("permissions")) { loanRepaymentsCount = retrieveNonDisbursementTransactions(loanRepayments); permissions = this.loanReadPlatformService.retrieveLoanPermissions(loanBasicDetails, isWaiveAllowed, loanRepaymentsCount); } convenienceDataRequired = true; } - if (responseParameters.contains("charges")) { + if (associationParameters.contains("charges")) { charges = this.chargeReadPlatformService.retrieveLoanCharges(loanId); if (CollectionUtils.isEmpty(charges)) { charges = null; // set back to null so doesnt appear in JSON is no charges exist. @@ -242,28 +210,21 @@ public class LoansApiResource { } } - Collection productOptions = new ArrayList(); + Collection productOptions = null; Collection loanTermFrequencyTypeOptions = null; - Collection repaymentFrequencyTypeOptions = new ArrayList(); + Collection repaymentFrequencyTypeOptions = null; Collection repaymentStrategyOptions = null; - - Collection interestRateFrequencyTypeOptions = new ArrayList(); - Collection amortizationTypeOptions = new ArrayList(); - Collection interestTypeOptions = new ArrayList(); - Collection interestCalculationPeriodTypeOptions = new ArrayList(); - Collection fundOptions = new ArrayList(); + Collection interestRateFrequencyTypeOptions = null; + Collection amortizationTypeOptions = null; + Collection interestTypeOptions = null; + Collection interestCalculationPeriodTypeOptions = null; + Collection fundOptions = null; + Collection allowedLoanOfficers = null; Collection chargeOptions = null; ChargeData chargeTemplate = null; - Collection allowedLoanOfficers = null; - + final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); if(template) { - responseParameters.addAll( - Arrays.asList("productOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", - "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", - "repaymentStrategyOptions", "chargeOptions", "chargeTemplate", "loanOfficerOptions") - ); - productOptions = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(); loanTermFrequencyTypeOptions = dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions(); repaymentFrequencyTypeOptions = dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions(); @@ -275,7 +236,8 @@ public class LoansApiResource { fundOptions = this.fundReadPlatformService.retrieveAllFunds(); repaymentStrategyOptions = this.dropdownReadPlatformService.retreiveTransactionProcessingStrategies(); - chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(); + final boolean feeChargesOnly = false; + chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly); allowedLoanOfficers = this.staffReadPlatformService.retrieveAllLoanOfficersByOffice(loanBasicDetails.getClientOfficeId()); chargeTemplate = this.chargeReadPlatformService.retrieveLoanChargeTemplate(); } @@ -461,9 +423,6 @@ public class LoansApiResource { @Context final UriInfo uriInfo) { final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - if (responseParameters.isEmpty()) { - responseParameters.addAll(typicalTransactionResponseParameters); - } final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); LoanTransactionData transactionData = null; @@ -493,9 +452,6 @@ public class LoansApiResource { @Context final UriInfo uriInfo) { final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - if (responseParameters.isEmpty()) { - responseParameters.addAll(typicalTransactionResponseParameters); - } final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); final LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransactionDetails(loanId, transactionId); @@ -530,9 +486,9 @@ public class LoansApiResource { @PathParam("loanId") final Long loanId, final String jsonRequestBody){ - LoanChargeCommand command = this.apiDataConversionService.convertJsonToLoanChargeCommand(null, loanId, jsonRequestBody); + final LoanChargeCommand command = this.apiDataConversionService.convertJsonToLoanChargeCommand(null, loanId, jsonRequestBody); - EntityIdentifier identifier = this.loanWritePlatformService.addLoanCharge(command); + final EntityIdentifier identifier = this.loanWritePlatformService.addLoanCharge(command); return Response.ok().entity(identifier).build(); } @@ -542,12 +498,14 @@ public class LoansApiResource { @Consumes({MediaType.APPLICATION_JSON}) @Produces({MediaType.APPLICATION_JSON}) public String retrieveNewLoanChargeDetails(@Context final UriInfo uriInfo) { - Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); + + final Set responseParameters = ApiParameterHelper.extractFieldsForResponseIfProvided(uriInfo.getQueryParameters()); - boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); + final boolean prettyPrint = ApiParameterHelper.prettyPrint(uriInfo.getQueryParameters()); - Collection chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(); - LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions); + final boolean feeChargesOnly = false; + final Collection chargeOptions = this.chargeReadPlatformService.retrieveLoanApplicableCharges(feeChargesOnly); + final LoanChargeData loanChargeTemplate = LoanChargeData.template(chargeOptions); return this.apiJsonSerializerService.serializeLoanChargeDataToJson(prettyPrint, responseParameters, loanChargeTemplate); } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/ChargeCommand.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/ChargeCommand.java index 189216fac..67fae2680 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/ChargeCommand.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/commands/ChargeCommand.java @@ -3,6 +3,9 @@ package org.mifosng.platform.api.commands; import java.math.BigDecimal; import java.util.Set; +/** + * Immutable data object for creating and modifying defined charges. + */ public class ChargeCommand { private final Long id; @@ -14,12 +17,22 @@ public class ChargeCommand { private final Integer chargeAppliesTo; private final Integer chargeCalculationType; + private final boolean penalty; private final boolean active; private final Set modifiedParameters; - public ChargeCommand(Set modifiedParameters, Long id, String name, BigDecimal amount, String currencyCode, - Integer chargeTimeType, Integer chargeAppliesTo, Integer chargeCalculationType, boolean active) { + public ChargeCommand( + final Set modifiedParameters, + final Long id, + final String name, + final BigDecimal amount, + final String currencyCode, + final Integer chargeTimeType, + final Integer chargeAppliesTo, + final Integer chargeCalculationType, + final boolean penalty, + final boolean active) { this.id = id; this.name = name; this.amount = amount; @@ -28,6 +41,7 @@ public class ChargeCommand { this.chargeAppliesTo = chargeAppliesTo; this.chargeCalculationType = chargeCalculationType; this.modifiedParameters = modifiedParameters; + this.penalty = penalty; this.active = active; } @@ -62,8 +76,16 @@ public class ChargeCommand { public Integer getChargeTimeType() { return chargeTimeType; } + + public boolean isPenalty() { + return penalty; + } + + public boolean isFee() { + return !penalty; + } - public boolean isActive() { + public boolean isActive() { return active; } @@ -91,7 +113,11 @@ public class ChargeCommand { return this.modifiedParameters.contains("chargeCalculationType"); } + public boolean isPenaltyChanged(){ + return this.modifiedParameters.contains("penalty"); + } + public boolean isActiveChanged(){ return this.modifiedParameters.contains("active"); } -} +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/ChargeData.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/ChargeData.java index c3dd1df18..7b9ddd948 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/ChargeData.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/ChargeData.java @@ -11,6 +11,7 @@ public class ChargeData { private final Long id; private final String name; private final boolean active; + private final boolean penalty; private final CurrencyData currency; private final BigDecimal amount; private final EnumOptionData chargeTimeType; @@ -27,22 +28,24 @@ public class ChargeData { final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, final List chargeTimeTypeOptions) { - return new ChargeData(null, null, null, null, null, null, null, false, + + return new ChargeData(null, null, null, null, null, null, null, false, false, currencyOptions, chargeCalculationTypeOptions, chargeAppliesToOptions, chargeTimeTypeOptions); } - public ChargeData(ChargeData charge, ChargeData template){ - this(charge.getId(), charge.getName(), charge.getAmount(), charge.getCurrency(), charge.getChargeTimeType(), - charge.getChargeAppliesTo(), charge.getChargeCalculationType(), charge.isActive(), - template.getCurrencyOptions(), template.getChargeCalculationTypeOptions(), - template.getChargeAppliesToOptions(), template.getChargeTimeTypeOptions()); + public ChargeData(final ChargeData charge, final ChargeData template){ + this(charge.id, charge.name, charge.amount, charge.currency, charge.chargeTimeType, + charge.chargeAppliesTo, charge.chargeCalculationType, + charge.penalty, charge.active, + template.currencyOptions, template.chargeCalculationTypeOptions, + template.chargeAppliesToOptions, template.chargeTimeTypeOptions); } public ChargeData(final Long id, final String name, final BigDecimal amount, final CurrencyData currency, final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, - final EnumOptionData chargeCalculationType, final boolean active) { + final EnumOptionData chargeCalculationType, final boolean penalty, final boolean active) { this.id = id; this.name = name; this.amount = amount; @@ -50,6 +53,7 @@ public class ChargeData { this.chargeTimeType = chargeTimeType; this.chargeAppliesTo = chargeAppliesTo; this.chargeCalculationType = chargeCalculationType; + this.penalty = penalty; this.active = active; this.currencyOptions = null; this.chargeCalculationTypeOptions = null; @@ -61,7 +65,9 @@ public class ChargeData { final BigDecimal amount, final CurrencyData currency, final EnumOptionData chargeTimeType, final EnumOptionData chargeAppliesTo, - final EnumOptionData chargeCalculationType, final boolean active, + final EnumOptionData chargeCalculationType, + final boolean penalty, + final boolean active, final List currencyOptions, final List chargeCalculationTypeOptions, final List chargeAppliesToOptions, @@ -73,6 +79,7 @@ public class ChargeData { this.chargeTimeType = chargeTimeType; this.chargeAppliesTo = chargeAppliesTo; this.chargeCalculationType = chargeCalculationType; + this.penalty = penalty; this.active = active; this.currencyOptions = currencyOptions; this.chargeCalculationTypeOptions = chargeCalculationTypeOptions; @@ -88,10 +95,10 @@ public class ChargeData { return name; } - public boolean isActive() { - return active; + public boolean isPenalty() { + return this.penalty; } - + public BigDecimal getAmount() { return amount; } @@ -104,34 +111,14 @@ public class ChargeData { return chargeTimeType; } - public EnumOptionData getChargeAppliesTo() { - return chargeAppliesTo; - } - public EnumOptionData getChargeCalculationType() { return chargeCalculationType; } - public List getCurrencyOptions() { - return currencyOptions; - } - - public List getChargeCalculationTypeOptions() { - return chargeCalculationTypeOptions; - } - - public List getChargeAppliesToOptions() { - return chargeAppliesToOptions; - } - - public List getChargeTimeTypeOptions() { - return chargeTimeTypeOptions; - } - @Override public boolean equals(Object obj) { ChargeData chargeData = (ChargeData) obj; - return this.id.equals(chargeData.getId()); + return this.id.equals(chargeData.id); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanBasicDetailsData.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanBasicDetailsData.java index 6d4da79d0..95dd94fc6 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanBasicDetailsData.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/data/LoanBasicDetailsData.java @@ -78,8 +78,8 @@ public class LoanBasicDetailsData { final Collection charges = new ArrayList(); for (ChargeData charge : product.charges()) { - charges.add(LoanChargeData.newChargeDetails(charge.getId(), charge.getName(), charge.getCurrency(), - charge.getAmount(), charge.getChargeTimeType(), charge.getChargeCalculationType())); + charges.add(LoanChargeData.newLoanChargeDetails(charge.getId(), charge.getName(), charge.getCurrency(), + charge.getAmount(), charge.getChargeTimeType(), charge.getChargeCalculationType(), charge.isPenalty())); } return new LoanBasicDetailsData( 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 af0f4b234..ea558b513 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 @@ -51,21 +51,25 @@ public class LoanChargeData { @SuppressWarnings("unused") private final Collection chargeOptions; + @SuppressWarnings("unused") + private final boolean penalty; + public static LoanChargeData template(final Collection chargeOptions){ - return new LoanChargeData(null,null,null,null,null,null,null, chargeOptions); + return new LoanChargeData(null,null,null,null,null,null,null, chargeOptions, false); } /** * used when populating with details from charge definition (for crud on charges) */ - public static LoanChargeData newChargeDetails( + public static LoanChargeData newLoanChargeDetails( final Long chargeId, final String name, final CurrencyData currency, final BigDecimal value, final EnumOptionData chargeTimeType, - final EnumOptionData chargeCalculationType) { - return new LoanChargeData(null, chargeId, name, currency, value, chargeTimeType, chargeCalculationType, null); + final EnumOptionData chargeCalculationType, + final boolean penalty) { + return new LoanChargeData(null, chargeId, name, currency, value, chargeTimeType, chargeCalculationType, null, penalty); } public LoanChargeData( @@ -80,7 +84,8 @@ public class LoanChargeData { final LocalDate dueAsOfDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage, - final BigDecimal amountPercentageAppliedTo) { + final BigDecimal amountPercentageAppliedTo, + final boolean penalty) { this.id = id; this.chargeId = chargeId; this.name = name; @@ -93,6 +98,7 @@ public class LoanChargeData { this.chargeCalculationType = chargeCalculationType; this.percentage = percentage; this.amountPercentageAppliedTo = amountPercentageAppliedTo; + this.penalty = penalty; if (chargeCalculationType != null && chargeCalculationType.getId().intValue() > 1) { this.amountOrPercentage = this.percentage; @@ -111,7 +117,8 @@ public class LoanChargeData { final BigDecimal amount, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType, - final Collection chargeOptions) { + final Collection chargeOptions, + final boolean penalty) { this.id = id; this.chargeId = chargeId; this.name = name; @@ -124,6 +131,7 @@ public class LoanChargeData { this.chargeCalculationType = chargeCalculationType; this.percentage = null; this.amountPercentageAppliedTo = null; + this.penalty = penalty; if (chargeCalculationType != null && chargeCalculationType.getId().intValue() > 1) { this.amountOrPercentage = this.percentage; 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 ecdeb8f38..86421077c 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 @@ -24,7 +24,6 @@ public class LoanSchedulePeriodData { private final BigDecimal principalOutstanding; @SuppressWarnings("unused") private final BigDecimal principalLoanBalanceOutstanding; - @SuppressWarnings("unused") private final BigDecimal interestOriginalDue; private final BigDecimal interestDue; @@ -32,14 +31,16 @@ public class LoanSchedulePeriodData { private final BigDecimal interestWaived; private final BigDecimal interestWrittenOff; private final BigDecimal interestOutstanding; - private final BigDecimal feeChargesDue; private final BigDecimal feeChargesPaid; - @SuppressWarnings("unused") private final BigDecimal feeChargesWaived; - @SuppressWarnings("unused") private final BigDecimal feeChargesWrittenOff; private final BigDecimal feeChargesOutstanding; + private final BigDecimal penaltyChargesDue; + private final BigDecimal penaltyChargesPaid; + private final BigDecimal penaltyChargesWaived; + private final BigDecimal penaltyChargesWrittenOff; + private final BigDecimal penaltyChargesOutstanding; @SuppressWarnings("unused") private final BigDecimal totalOriginalDueForPeriod; @@ -53,15 +54,17 @@ public class LoanSchedulePeriodData { private final BigDecimal totalWrittenOffForPeriod; private final BigDecimal totalOutstandingForPeriod; private final BigDecimal totalOverdue; + @SuppressWarnings("unused") + private final BigDecimal totalActualCostOfLoanForPeriod; public static LoanSchedulePeriodData disbursementOnlyPeriod( final LocalDate disbursementDate, final BigDecimal principalDisbursed, - final BigDecimal chargesDueAtTimeOfDisbursement, + final BigDecimal feeChargesDueAtTimeOfDisbursement, final boolean isDisbursed) { Integer periodNumber = Integer.valueOf(0); LocalDate from = null; - return new LoanSchedulePeriodData(periodNumber, from, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement, isDisbursed); + return new LoanSchedulePeriodData(periodNumber, from, disbursementDate, principalDisbursed, feeChargesDueAtTimeOfDisbursement, isDisbursed); } public static LoanSchedulePeriodData repaymentOnlyPeriod( @@ -71,10 +74,12 @@ public class LoanSchedulePeriodData { final BigDecimal principalDue, final BigDecimal principalOutstanding, final BigDecimal interestDueOnPrincipalOutstanding, - final BigDecimal chargesDueForPeriod, + final BigDecimal feeChargesDueForPeriod, + final BigDecimal penaltyChargesDueForPeriod, final BigDecimal totalDueForPeriod) { - return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, principalDue, principalOutstanding, interestDueOnPrincipalOutstanding, chargesDueForPeriod, totalDueForPeriod); + return new LoanSchedulePeriodData(periodNumber, fromDate, dueDate, principalDue, principalOutstanding, + interestDueOnPrincipalOutstanding, feeChargesDueForPeriod, penaltyChargesDueForPeriod, totalDueForPeriod); } public static LoanSchedulePeriodData repaymentPeriodWithPayments( @@ -97,17 +102,24 @@ public class LoanSchedulePeriodData { final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff, final BigDecimal feeChargesOutstanding, + final BigDecimal penaltyChargesDue, + final BigDecimal penaltyChargesPaid, + final BigDecimal penaltyChargesWaived, + final BigDecimal penaltyChargesWrittenOff, + final BigDecimal penaltyChargesOutstanding, final BigDecimal totalDueForPeriod, final BigDecimal totalPaid, final BigDecimal totalWaived, final BigDecimal totalWrittenOff, - final BigDecimal totalOutstanding) { + final BigDecimal totalOutstanding, + final BigDecimal totalActualCostOfLoanForPeriod) { 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); + penaltyChargesDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, + totalDueForPeriod, totalPaid, totalWaived, totalWrittenOff, totalOutstanding, totalActualCostOfLoanForPeriod); } /* @@ -146,22 +158,29 @@ public class LoanSchedulePeriodData { this.feeChargesDue = chargesDueAtTimeOfDisbursement; if (isDisbursed) { this.feeChargesPaid = chargesDueAtTimeOfDisbursement; - this.feeChargesWaived = BigDecimal.ZERO; - this.feeChargesWrittenOff = BigDecimal.ZERO; - this.feeChargesOutstanding = BigDecimal.ZERO; + this.feeChargesWaived = null; + this.feeChargesWrittenOff = null; + this.feeChargesOutstanding = null; } else { - this.feeChargesPaid = BigDecimal.ZERO; - this.feeChargesWaived = BigDecimal.ZERO; - this.feeChargesWrittenOff = BigDecimal.ZERO; + this.feeChargesPaid = null; + this.feeChargesWaived = null; + this.feeChargesWrittenOff = null; this.feeChargesOutstanding = chargesDueAtTimeOfDisbursement; } + this.penaltyChargesDue = null; + this.penaltyChargesPaid = null; + this.penaltyChargesWaived = null; + this.penaltyChargesWrittenOff = null; + this.penaltyChargesOutstanding = null; + this.totalOriginalDueForPeriod = chargesDueAtTimeOfDisbursement; this.totalDueForPeriod = chargesDueAtTimeOfDisbursement; this.totalPaidForPeriod = this.feeChargesPaid; this.totalWaivedForPeriod = null; this.totalWrittenOffForPeriod = null; this.totalOutstandingForPeriod = this.feeChargesOutstanding; + this.totalActualCostOfLoanForPeriod = this.feeChargesDue; if (dueDate.isBefore(new LocalDate())) { this.totalOverdue = this.totalOutstandingForPeriod; } else { @@ -176,7 +195,8 @@ public class LoanSchedulePeriodData { final BigDecimal principalOriginalDue, final BigDecimal principalOutstanding, final BigDecimal interestDueOnPrincipalOutstanding, - final BigDecimal chargesDueForPeriod, + final BigDecimal feeChargesDueForPeriod, + final BigDecimal penaltyChargesDueForPeriod, final BigDecimal totalDueForPeriod) { this.period = periodNumber; this.fromDate = fromDate; @@ -201,18 +221,26 @@ public class LoanSchedulePeriodData { this.interestWrittenOff = null; this.interestOutstanding = interestDueOnPrincipalOutstanding; - this.feeChargesDue = chargesDueForPeriod; + this.feeChargesDue = feeChargesDueForPeriod; this.feeChargesPaid = null; this.feeChargesWaived = null; this.feeChargesWrittenOff = null; this.feeChargesOutstanding = null; + this.penaltyChargesDue = penaltyChargesDueForPeriod; + this.penaltyChargesPaid = null; + this.penaltyChargesWaived = null; + this.penaltyChargesWrittenOff = null; + this.penaltyChargesOutstanding = null; + this.totalOriginalDueForPeriod = totalDueForPeriod; this.totalDueForPeriod = totalDueForPeriod; this.totalPaidForPeriod = BigDecimal.ZERO; this.totalWaivedForPeriod = null; this.totalWrittenOffForPeriod = null; this.totalOutstandingForPeriod = totalDueForPeriod; + this.totalActualCostOfLoanForPeriod = interestDueOnPrincipalOutstanding.add(feeChargesDueForPeriod); + if (dueDate.isBefore(new LocalDate())) { this.totalOverdue = this.totalOutstandingForPeriod; } else { @@ -242,11 +270,17 @@ public class LoanSchedulePeriodData { final BigDecimal feeChargesWaived, final BigDecimal feeChargesWrittenOff, final BigDecimal feeChargesOutstanding, + final BigDecimal penaltyChargesDue, + final BigDecimal penaltyChargesPaid, + final BigDecimal penaltyChargesWaived, + final BigDecimal penaltyChargesWrittenOff, + final BigDecimal penaltyChargesOutstanding, final BigDecimal totalDueForPeriod, final BigDecimal totalPaid, final BigDecimal totalWaived, final BigDecimal totalWrittenOff, - final BigDecimal totalOutstanding) { + final BigDecimal totalOutstanding, + final BigDecimal totalActualCostOfLoanForPeriod) { this.period = periodNumber; this.fromDate = fromDate; this.dueDate = dueDate; @@ -276,12 +310,19 @@ public class LoanSchedulePeriodData { this.feeChargesWrittenOff = feeChargesWrittenOff; this.feeChargesOutstanding = feeChargesOutstanding; + this.penaltyChargesDue = penaltyChargesDue; + this.penaltyChargesPaid = penaltyChargesPaid; + this.penaltyChargesWaived = penaltyChargesWaived; + this.penaltyChargesWrittenOff = penaltyChargesWrittenOff; + this.penaltyChargesOutstanding = penaltyChargesOutstanding; + this.totalOriginalDueForPeriod = totalDueForPeriod; this.totalDueForPeriod = totalDueForPeriod; this.totalPaidForPeriod = totalPaid; this.totalWaivedForPeriod = totalWaived; this.totalWrittenOffForPeriod = totalWrittenOff; this.totalOutstandingForPeriod = totalOutstanding; + this.totalActualCostOfLoanForPeriod = totalActualCostOfLoanForPeriod; if (dueDate.isBefore(new LocalDate())) { this.totalOverdue = this.totalOutstandingForPeriod; @@ -310,6 +351,10 @@ public class LoanSchedulePeriodData { return this.period; } + public LocalDate periodFromDate() { + return this.fromDate; + } + public LocalDate periodDueDate() { return this.dueDate; } @@ -358,17 +403,45 @@ public class LoanSchedulePeriodData { return defaultToZeroIfNull(this.interestOutstanding); } - public BigDecimal chargesDue() { + public BigDecimal feeChargesDue() { return defaultToZeroIfNull(this.feeChargesDue); } + + public BigDecimal feeChargesWaived() { + return defaultToZeroIfNull(this.feeChargesWaived); + } + + public BigDecimal feeChargesWrittenOff() { + return defaultToZeroIfNull(this.feeChargesWrittenOff); + } - public BigDecimal chargesPaid() { + public BigDecimal feeChargesPaid() { return defaultToZeroIfNull(this.feeChargesPaid); } - public BigDecimal chargesOutstanding() { + public BigDecimal feeChargesOutstanding() { return defaultToZeroIfNull(this.feeChargesOutstanding); } + + public BigDecimal penaltyChargesDue() { + return defaultToZeroIfNull(this.penaltyChargesDue); + } + + public BigDecimal penaltyChargesWaived() { + return defaultToZeroIfNull(this.penaltyChargesWaived); + } + + public BigDecimal penaltyChargesWrittenOff() { + return defaultToZeroIfNull(this.penaltyChargesWrittenOff); + } + + public BigDecimal penaltyChargesPaid() { + return defaultToZeroIfNull(this.penaltyChargesPaid); + } + + public BigDecimal penaltyChargesOutstanding() { + return defaultToZeroIfNull(this.penaltyChargesOutstanding); + } public BigDecimal totalOverdue() { return defaultToZeroIfNull(this.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 d028e546e..956498d91 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 @@ -72,31 +72,32 @@ public class ApiDataConversionServiceImpl implements ApiDataConversionService { throw new InvalidJsonException(); } - Type typeOfMap = new TypeToken>(){}.getType(); - Map requestMap = gsonConverter.fromJson(json, typeOfMap); + final Type typeOfMap = new TypeToken>(){}.getType(); + final Map requestMap = gsonConverter.fromJson(json, typeOfMap); - - Set supportedParams = new HashSet(Arrays.asList("name", "amount", "locale", "currencyCode", + final Set supportedParams = new HashSet( + Arrays.asList("name", "amount", "locale", "currencyCode", "currencyOptions", "chargeAppliesTo", "chargeTimeType", - "chargeCalculationType", "chargeCalculationTypeOptions", "active" + "chargeCalculationType", "chargeCalculationTypeOptions", "penalty", "active" )); checkForUnsupportedParameters(requestMap, supportedParams); Set modifiedParameters = new HashSet(); - String name = extractStringParameter("name", requestMap, modifiedParameters); - BigDecimal amount = extractBigDecimalParameter("amount", requestMap, modifiedParameters); - String currencyCode = extractStringParameter("currencyCode", requestMap, modifiedParameters); + final String name = extractStringParameter("name", requestMap, modifiedParameters); + final BigDecimal amount = extractBigDecimalParameter("amount", requestMap, modifiedParameters); + final String currencyCode = extractStringParameter("currencyCode", requestMap, modifiedParameters); - Integer chargeTimeType = extractIntegerParameter("chargeTimeType", requestMap, modifiedParameters); - Integer chargeAppliesTo = extractIntegerParameter("chargeAppliesTo", requestMap, modifiedParameters); - Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", requestMap, modifiedParameters); + final Integer chargeTimeType = extractIntegerParameter("chargeTimeType", requestMap, modifiedParameters); + final Integer chargeAppliesTo = extractIntegerParameter("chargeAppliesTo", requestMap, modifiedParameters); + final Integer chargeCalculationType = extractIntegerParameter("chargeCalculationType", requestMap, modifiedParameters); - boolean active = extractBooleanParameter("active", requestMap, modifiedParameters); + final boolean penalty = extractBooleanParameter("penalty", requestMap, modifiedParameters); + final boolean active = extractBooleanParameter("active", requestMap, modifiedParameters); return new ChargeCommand(modifiedParameters, resourceIdentifier, name, amount, - currencyCode, chargeTimeType, chargeAppliesTo, chargeCalculationType, active); + currencyCode, chargeTimeType, chargeAppliesTo, chargeCalculationType, penalty, active); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonApiJsonSerializerService.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonApiJsonSerializerService.java index 08cbcdcaa..895c9c102 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonApiJsonSerializerService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonApiJsonSerializerService.java @@ -188,7 +188,7 @@ public class GoogleGsonApiJsonSerializerService implements ApiJsonSerializerServ Arrays.asList("id", "type", "date", "currency", "amount")); private static final Set CHARGES_DATA_PARAMETERS = new HashSet( - Arrays.asList("id", "name", "amount", "currency", "active", + Arrays.asList("id", "name", "amount", "currency", "penalty", "active", "chargeAppliesTo", "chargeTimeType", "chargeCalculationType", "chargeCalculationTypeOptions", "chargeAppliesToOptions", "chargeTimeTypeOptions", "currencyOptions")); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonSerializerHelper.java b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonSerializerHelper.java index 34ce7800f..99548e4db 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonSerializerHelper.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/api/infrastructure/GoogleGsonSerializerHelper.java @@ -38,7 +38,7 @@ public class GoogleGsonSerializerHelper { if (!responseParameters.isEmpty()) { - // strip out all know support parameters from expected response to + // strip out all known support parameters from expected response to // see if unsupported parameters requested for response. final Set differentParametersDetectedSet = new HashSet(responseParameters); differentParametersDetectedSet.removeAll(supportedParameters); diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/charge/domain/Charge.java b/mifosng-provider/src/main/java/org/mifosng/platform/charge/domain/Charge.java index 4717fba62..78eed2a4b 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/charge/domain/Charge.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/charge/domain/Charge.java @@ -32,15 +32,23 @@ public class Charge extends AbstractAuditableCustom { @Column(name = "charge_calculation_enum") private Integer chargeCalculation; + @Column(name = "is_penalty", nullable = false) + private boolean penalty; + @Column(name = "is_active", nullable = false) private boolean active; @Column(name = "is_deleted", nullable=false) private boolean deleted = false; - public static Charge createNew(final String name, final BigDecimal amount, final String currencyCode, - final ChargeAppliesTo chargeAppliesTo, final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculationType, final boolean active) { - return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTime, chargeCalculationType, active); + public static Charge createNew( + final String name, final BigDecimal amount, final String currencyCode, + final ChargeAppliesTo chargeAppliesTo, + final ChargeTimeType chargeTime, + final ChargeCalculationType chargeCalculationType, + final boolean penalty, + final boolean active) { + return new Charge(name, amount, currencyCode, chargeAppliesTo, chargeTime, chargeCalculationType, penalty, active); } protected Charge() { @@ -49,13 +57,14 @@ public class Charge extends AbstractAuditableCustom { private Charge(final String name, final BigDecimal amount, final String currencyCode, final ChargeAppliesTo chargeAppliesTo, final ChargeTimeType chargeTime, - final ChargeCalculationType chargeCalculationType, final boolean active) { + final ChargeCalculationType chargeCalculationType, final boolean penalty, boolean active) { this.name = name; this.amount = amount; this.currencyCode = currencyCode; this.chargeAppliesTo = chargeAppliesTo.getValue(); this.chargeTime = chargeTime.getValue(); this.chargeCalculation = chargeCalculationType.getValue(); + this.penalty = penalty; this.active = active; } @@ -82,6 +91,10 @@ public class Charge extends AbstractAuditableCustom { public boolean isActive() { return active; } + + public boolean isPenalty() { + return penalty; + } public boolean isDeleted() { return deleted; @@ -126,4 +139,4 @@ public class Charge extends AbstractAuditableCustom { this.deleted = true; this.name = this.getId() + "_" + this.name; } -} +} \ No newline at end of file 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 09e8cfbde..6484a1e35 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 @@ -27,6 +27,10 @@ public class ChargeCommandValidator { baseDataValidator.reset().parameter("chargeAppliesTo").value(command.getChargeAppliesTo()).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 (command.isPenalty() && command.getChargeTimeType().equals(Integer.valueOf(1))) { + // FIXME - KW - cannot have penalty charge at time of disbursement + } 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/charge/service/ChargeReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformService.java index 0cdc86628..bf1cb22aa 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/charge/service/ChargeReadPlatformService.java @@ -13,7 +13,7 @@ public interface ChargeReadPlatformService { ChargeData retrieveNewChargeDetails(); - Collection retrieveLoanApplicableCharges(); + Collection retrieveLoanApplicableCharges(boolean feeChargesOnly); Collection retrieveLoanProductCharges(Long loanProductId); 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 bbd526ed9..c9cafbb3e 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 @@ -101,18 +101,22 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final LoanChargeMapper rm = new LoanChargeMapper(); - final String sql = "select " + rm.loanChargeSchema() + " where c.is_deleted=0 and lc.loan_id=?"; + final String sql = "select " + rm.loanChargeSchema() + " where c.is_deleted=0 and lc.loan_id=? " + + " order by lc.charge_time_enum ASC, lc.due_for_collection_as_of_date ASC, lc.is_penalty ASC"; return this.jdbcTemplate.query(sql, rm, new Object[] {loanId}); } @Override - public Collection retrieveLoanApplicableCharges() { + public Collection retrieveLoanApplicableCharges(final boolean feeChargesOnly) { this.context.authenticatedUser(); final ChargeMapper rm = new ChargeMapper(); - final String sql = "select " + rm.chargeSchema() + " where c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=? order by c.name "; + String sql = "select " + rm.chargeSchema() + " where c.is_deleted=0 and c.is_active=1 and c.charge_applies_to_enum=? order by c.name "; + if (feeChargesOnly) { + sql = "select " + rm.chargeSchema() + " where c.is_deleted=0 and c.is_active=1 and c.is_penalty=0 and c.charge_applies_to_enum=? order by c.name "; + } return this.jdbcTemplate.query(sql, rm, new Object[] {ChargeAppliesTo.LOAN.getValue()}); } @@ -132,7 +136,7 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService public String chargeSchema(){ return "c.id as id, c.name as name, c.amount as amount, c.currency_code as currencyCode, " + "c.charge_applies_to_enum as chargeAppliesTo, c.charge_time_enum as chargeTime, " + - "c.charge_calculation_enum as chargeCalculation, c.is_active as active, oc.name as currencyName, " + + "c.charge_calculation_enum as chargeCalculation, c.is_penalty as penalty, c.is_active as active, oc.name as currencyName, " + "oc.decimal_places as currencyDecimalPlaces, oc.display_symbol as currencyDisplaySymbol, " + "oc.internationalized_name_code as currencyNameCode from m_charge c " + "join m_organisation_currency oc on c.currency_code = oc.code"; @@ -165,9 +169,10 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final int chargeCalculation = rs.getInt("chargeCalculation"); final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); + final boolean penalty = rs.getBoolean("penalty"); final boolean active = rs.getBoolean("active"); - return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType, active); + return new ChargeData(id, name, amount, currency, chargeTimeType, chargeAppliesToType, chargeCalculationType, penalty, active); } } @@ -178,6 +183,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.is_penalty as penalty, " + "lc.due_for_collection_as_of_date as dueAsOfDate, " + "lc.charge_calculation_enum as chargeCalculation, " + "c.currency_code as currencyCode, oc.name as currencyName, " + @@ -194,14 +200,11 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final Long chargeId = rs.getLong("chargeId"); final String name = rs.getString("name"); final BigDecimal amount = rs.getBigDecimal("amountDue"); - BigDecimal amountPaid = rs.getBigDecimal("amountPaid"); - if (amountPaid == null) { - amountPaid = BigDecimal.ZERO; - } + final BigDecimal amountPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPaid"); final BigDecimal amountOutstanding = rs.getBigDecimal("amountOutstanding"); - final BigDecimal percentageOf = rs.getBigDecimal("percentageOf"); - final BigDecimal amountPercentageAppliedTo = rs.getBigDecimal("amountPercentageAppliedTo"); + final BigDecimal percentageOf = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "percentageOf"); + final BigDecimal amountPercentageAppliedTo = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "amountPercentageAppliedTo"); final String currencyCode = rs.getString("currencyCode"); final String currencyName = rs.getString("currencyName"); @@ -219,8 +222,9 @@ public class ChargeReadPlatformServiceImpl implements ChargeReadPlatformService final int chargeCalculation = rs.getInt("chargeCalculation"); final EnumOptionData chargeCalculationType = ChargeEnumerations.chargeCalculationType(chargeCalculation); - - return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo); + final boolean penalty = rs.getBoolean("penalty"); + + return new LoanChargeData(id, chargeId, name, currency, amount, amountPaid, amountOutstanding, chargeTimeType, dueAsOfDate, chargeCalculationType, percentageOf, amountPercentageAppliedTo, penalty); } } } \ 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 d548e0e1d..2d64bb28e 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 @@ -39,7 +39,7 @@ public class ChargeWritePlatformServiceJpaRepositoryImpl implements ChargeWriteP final ChargeCalculationType chargeCalculationType = ChargeCalculationType.fromInt(command.getChargeCalculationType()); final Charge charge = Charge.createNew(command.getName(), command.getAmount(), command.getCurrencyCode(), - chargeAppliesTo, chargeTimeType, chargeCalculationType, command.isActive()); + chargeAppliesTo, chargeTimeType, chargeCalculationType, command.isPenalty(), command.isActive()); this.chargeRepository.saveAndFlush(charge); 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 986e81c0e..55b2e59d0 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 @@ -378,11 +378,14 @@ public class Loan extends AbstractAuditableCustom { if (scheduledLoanInstallment.isRepaymentPeriod()) { LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment( - this, scheduledLoanInstallment.periodNumber(), + this, + scheduledLoanInstallment.periodNumber(), + scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), - scheduledLoanInstallment.chargesDue()); + scheduledLoanInstallment.feeChargesDue(), + scheduledLoanInstallment.penaltyChargesDue()); 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 644261e0c..e37e502b1 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 @@ -60,6 +60,9 @@ public class LoanCharge extends AbstractPersistable { @SuppressWarnings("unused") @Column(name = "amount_outstanding_derived", scale = 6, precision = 19, nullable = false) private BigDecimal amountOutstanding; + + @Column(name = "is_penalty", nullable=false) + private boolean penaltyCharge = false; @SuppressWarnings("unused") @Column(name = "is_paid_derived", nullable=false) @@ -83,11 +86,11 @@ public class LoanCharge extends AbstractPersistable { protected LoanCharge() { // } - public LoanCharge(final Charge chargeDefinition, final LoanChargeCommand command, final BigDecimal loanPrincipal) { this.loan = null; this.charge = chargeDefinition; + this.penaltyCharge = chargeDefinition.isPenalty(); if (command.isChargeTimeTypeChanged()) { this.chargeTime = command.getChargeTimeType(); @@ -125,7 +128,7 @@ public class LoanCharge extends AbstractPersistable { private LoanCharge(final Loan loan, final Charge chargeDefinition, final LoanChargeCommand command) { this.loan = loan; this.charge = chargeDefinition; - + this.penaltyCharge = chargeDefinition.isPenalty(); if (command.isChargeTimeTypeChanged()){ this.chargeTime = command.getChargeTimeType(); } else { @@ -344,4 +347,12 @@ public class LoanCharge extends AbstractPersistable { final LocalDate specifiedDueDate) { return specifiedDueDate != null && fromNotInclusive.isBefore(specifiedDueDate) && (toInclusive.isAfter(specifiedDueDate) || toInclusive.isEqual(specifiedDueDate)); } + + public boolean isFeeCharge() { + return !this.penaltyCharge; + } + + public boolean isPenaltyCharge() { + return this.penaltyCharge; + } } \ 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 db3410753..3605fbb3f 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 @@ -25,85 +25,89 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus @JoinColumn(name = "loan_id") private Loan loan; - @Column(name = "installment") + @Column(name = "installment", nullable=false) private final Integer installmentNumber; - @Column(name = "principal_amount", scale=6, precision=19) + @Temporal(TemporalType.DATE) + @Column(name = "fromdate", nullable=true) + private final Date fromDate; + + @Temporal(TemporalType.DATE) + @Column(name = "duedate", nullable=false) + private final Date dueDate; + + @Column(name = "principal_amount", scale=6, precision=19, nullable=true) private BigDecimal principal; - @Column(name = "interest_amount", scale=6, precision=19) - private BigDecimal interest; - - @Column(name = "fee_charges_amount", scale=6, precision=19) - private BigDecimal feeCharges; - - @Column(name = "fee_charges_completed_derived", scale=6, precision=19) - private BigDecimal feeChargesCompleted; - - @Column(name = "fee_charges_writtenoff_derived", scale=6, precision=19) - private BigDecimal feeChargesWrittenOff; - - @Column(name = "fee_charges_waived_derived", scale=6, precision=19) - private BigDecimal feeChargesWaived; - - @Column(name = "penalty_charges_amount", scale=6, precision=19) - private BigDecimal penaltyCharges; - - @Column(name = "penalty_charges_completed_derived", scale=6, precision=19) - private BigDecimal penaltyChargesCompleted; - - @Column(name = "penalty_charges_writtenoff_derived", scale=6, precision=19) - private BigDecimal penaltyChargesWrittenOff; - - @Column(name = "penalty_charges_waived_derived", scale=6, precision=19) - private BigDecimal penaltyChargesWaived; - - @Column(name = "principal_completed_derived", scale=6, precision=19) + @Column(name = "principal_completed_derived", scale=6, precision=19, nullable=true) private BigDecimal principalCompleted; - @Column(name = "principal_writtenoff_derived", scale=6, precision=19) + @Column(name = "principal_writtenoff_derived", scale=6, precision=19, nullable=true) private BigDecimal principalWrittenOff; - @Column(name = "interest_completed_derived", scale = 6, precision = 19) + @Column(name = "interest_amount", scale=6, precision=19, nullable=true) + private BigDecimal interest; + + @Column(name = "interest_completed_derived", scale = 6, precision = 19, nullable=true) private BigDecimal interestCompleted; - @Column(name = "interest_waived_derived", scale = 6, precision = 19) + @Column(name = "interest_waived_derived", scale = 6, precision = 19, nullable=true) private BigDecimal interestWaived; - @Column(name = "interest_writtenoff_derived", scale=6, precision=19) + @Column(name = "interest_writtenoff_derived", scale=6, precision=19, nullable=true) private BigDecimal interestWrittenOff; + + @Column(name = "fee_charges_amount", scale=6, precision=19, nullable=true) + private BigDecimal feeCharges; - @Column(name="completed_derived") + @Column(name = "fee_charges_completed_derived", scale=6, precision=19, nullable=true) + private BigDecimal feeChargesCompleted; + + @Column(name = "fee_charges_writtenoff_derived", scale=6, precision=19, nullable=true) + private BigDecimal feeChargesWrittenOff; + + @Column(name = "fee_charges_waived_derived", scale=6, precision=19, nullable=true) + private BigDecimal feeChargesWaived; + + @Column(name = "penalty_charges_amount", scale=6, precision=19, nullable=true) + private BigDecimal penaltyCharges; + + @Column(name = "penalty_charges_completed_derived", scale=6, precision=19, nullable=true) + private BigDecimal penaltyChargesCompleted; + + @Column(name = "penalty_charges_writtenoff_derived", scale=6, precision=19, nullable=true) + private BigDecimal penaltyChargesWrittenOff; + + @Column(name = "penalty_charges_waived_derived", scale=6, precision=19, nullable=true) + private BigDecimal penaltyChargesWaived; + + @Column(name="completed_derived", nullable=false) private boolean completed; - @Temporal(TemporalType.DATE) - @Column(name = "duedate") - private final Date dueDate; - protected LoanRepaymentScheduleInstallment() { - this.loan = null; - this.installmentNumber = null; - this.dueDate = null; - this.principal = null; - this.interest = null; - this.principalCompleted = null; - this.interestCompleted = null; + this.installmentNumber = null; + this.fromDate = null; + this.dueDate = null; this.completed = false; } public LoanRepaymentScheduleInstallment( final Loan loan, final Integer installmentNumber, + final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, - final BigDecimal feeCharges) { + final BigDecimal feeCharges, + final BigDecimal penaltyCharges) { this.loan = loan; this.installmentNumber = installmentNumber; + this.fromDate = fromDate.toDateMidnight().toDate(); this.dueDate = dueDate.toDateMidnight().toDate(); this.principal = defaultToNullIfZero(principal); this.interest = defaultToNullIfZero(interest); this.feeCharges = defaultToNullIfZero(feeCharges); + this.penaltyCharges = defaultToNullIfZero(penaltyCharges); this.completed = false; } @@ -219,11 +223,11 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus } public void resetDerivedComponents() { - this.principalCompleted = BigDecimal.ZERO; - this.principalWrittenOff = BigDecimal.ZERO; - this.interestCompleted = BigDecimal.ZERO; - this.interestWaived = BigDecimal.ZERO; - this.interestWrittenOff = BigDecimal.ZERO; + this.principalCompleted = null; + this.principalWrittenOff = null; + this.interestCompleted = null; + this.interestWaived = null; + this.interestWrittenOff = null; this.completed = false; } @@ -241,6 +245,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus interestPortionOfTransaction = interestPortionOfTransaction.plus(transactionAmountRemaining); } + this.interestCompleted = defaultToNullIfZero(this.interestCompleted); + this.completed = getTotalOutstanding(currency).isZero(); return interestPortionOfTransaction; @@ -260,6 +266,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus principalPortionOfTransaction = principalPortionOfTransaction.plus(transactionAmountRemaining); } + this.principalCompleted = defaultToNullIfZero(this.principalCompleted); + this.completed = getTotalOutstanding(currency).isZero(); return principalPortionOfTransaction; @@ -278,6 +286,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus waivedInterestPortionOfTransaction = waivedInterestPortionOfTransaction.plus(transactionAmountRemaining); } + this.interestWaived = defaultToNullIfZero(this.interestWaived); + this.completed = getTotalOutstanding(currency).isZero(); return waivedInterestPortionOfTransaction; @@ -286,7 +296,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus public Money writeOffOutstandingPrincipal(final MonetaryCurrency currency) { final Money principalDue = getPrincipalOutstanding(currency); - this.principalWrittenOff = principalDue.getAmount(); + this.principalWrittenOff = defaultToNullIfZero(principalDue.getAmount()); this.completed = getTotalOutstanding(currency).isZero(); return principalDue; @@ -295,7 +305,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus public Money writeOffOutstandingInterest(final MonetaryCurrency currency) { final Money interestDue = getInterestOutstanding(currency); - this.interestWrittenOff = interestDue.getAmount(); + this.interestWrittenOff = defaultToNullIfZero(interestDue.getAmount()); this.completed = getTotalOutstanding(currency).isZero(); return interestDue; @@ -305,7 +315,8 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus return this.getDueDate().isBefore(transactionDate); } - public void updateChargePortion(final Money feeChargesDue) { + public void updateChargePortion(final Money feeChargesDue, final Money penaltyChargesDue) { this.feeCharges = defaultToNullIfZero(feeChargesDue.getAmount()); + this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.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 80ec8f6aa..730fad175 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 @@ -29,7 +29,6 @@ public class LoanScheduleWrapper { /** * 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, @@ -42,15 +41,15 @@ public class LoanScheduleWrapper { 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); -// } + final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, currency); + final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), loanCharges, currency); + period.updateChargePortion(feeChargesDueForRepaymentPeriod, penaltyChargesDueForRepaymentPeriod); + startDate = period.getDueDate(); } } - private Money cumulativeChargesDueWithin( + private Money cumulativeFeeChargesDueWithin( final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, @@ -59,7 +58,24 @@ public class LoanScheduleWrapper { Money cumulative = Money.zero(monetaryCurrency); for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd)) { + if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isFeeCharge()) { + cumulative = cumulative.plus(loanCharge.amount()); + } + } + + return cumulative; + } + + private Money cumulativePenaltyChargesDueWithin( + 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) && loanCharge.isPenaltyCharge()) { cumulative = cumulative.plus(loanCharge.amount()); } } 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 75d180244..498d4948b 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 @@ -137,11 +137,14 @@ public class LoanAssembler { if (scheduledLoanInstallment.isRepaymentPeriod()) { final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment( - loan, scheduledLoanInstallment.periodNumber(), + loan, + scheduledLoanInstallment.periodNumber(), + scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), - scheduledLoanInstallment.chargesDue()); + scheduledLoanInstallment.feeChargesDue(), + scheduledLoanInstallment.penaltyChargesDue()); loan.addRepaymentScheduleInstallment(installment); } 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 b75542baf..b0c6428ec 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 @@ -98,7 +98,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { public LoanScheduleData retrieveRepaymentSchedule(final Long loanId, final CurrencyData currency, final DisbursementData disbursement, - final BigDecimal totalChargesAtDisbursement, + final BigDecimal totalFeeChargesAtDisbursement, final BigDecimal inArrearsTolerance) { try { @@ -107,7 +107,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LoanScheduleMapper rm = new LoanScheduleMapper(disbursement); final String sql = "select " + rm.loanScheduleSchema() + " where l.id = ? order by ls.loan_id, ls.installment"; - final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(disbursement.disbursementDate(), disbursement.amount(), totalChargesAtDisbursement, disbursement.isDisbursed()); + final LoanSchedulePeriodData disbursementPeriod = LoanSchedulePeriodData.disbursementOnlyPeriod(disbursement.disbursementDate(), disbursement.amount(), totalFeeChargesAtDisbursement, disbursement.isDisbursed()); final Collection repaymentSchedulePeriods = this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); @@ -128,16 +128,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final BigDecimal cumulativeInterestWaived = wrapper.deriveCumulativeInterestWaived(); final BigDecimal cumulativeInterestWrittenOff = wrapper.deriveCumulativeInterestWrittenOff(); final BigDecimal cumulativeInterestOutstanding = wrapper.deriveCumulativeInterestOutstanding(); - final BigDecimal cumulativeChargesToDate = wrapper.deriveCumulativeChargesToDate(); - final BigDecimal cumulativeChargesPaid = wrapper.deriveCumulativeChargesPaid(); - final BigDecimal cumulativeChargesOutstanding = wrapper.deriveCumulativeChargesOutstanding(); + final BigDecimal cumulativeFeeChargesToDate = wrapper.deriveCumulativeFeeChargesToDate(); + final BigDecimal cumulativeFeeChargesPaid = wrapper.deriveCumulativeFeeChargesPaid(); + final BigDecimal cumulativeFeeChargesOutstanding = wrapper.deriveCumulativeFeeChargesOutstanding(); + final BigDecimal cumulativePenaltyChargesToDate = wrapper.deriveCumulativePenaltyChargesToDate(); + final BigDecimal cumulativePenaltyChargesPaid = wrapper.deriveCumulativePenaltyChargesPaid(); + final BigDecimal cumulativePenaltyChargesOutstanding = wrapper.deriveCumulativePenaltyChargesOutstanding(); - final BigDecimal totalCostOfLoan = cumulativeInterestExpected.add(cumulativeChargesToDate); + final BigDecimal totalCostOfLoan = cumulativeInterestExpected.add(cumulativeFeeChargesToDate).add(cumulativePenaltyChargesToDate); final BigDecimal totalExpectedRepayment = cumulativePrincipalDisbursed.add(totalCostOfLoan); - final BigDecimal totalPaidToDate = cumulativePrincipalPaid.add(cumulativeInterestPaid).add(cumulativeChargesPaid); + final BigDecimal totalPaidToDate = cumulativePrincipalPaid.add(cumulativeInterestPaid).add(cumulativeFeeChargesPaid); final BigDecimal totalWaivedToDate = cumulativeInterestWaived; final BigDecimal totalWrittenOffToDate = cumulativePrincipalWrittenOff.add(cumulativeInterestWrittenOff); - final BigDecimal totalOutstanding = cumulativePrincipalOutstanding.add(cumulativeInterestOutstanding).add(cumulativeChargesOutstanding); + final BigDecimal totalOutstanding = cumulativePrincipalOutstanding.add(cumulativeInterestOutstanding).add(cumulativeFeeChargesOutstanding); final BigDecimal totalOverdue = wrapper.deriveCumulativeTotalOverdue(); @@ -154,7 +157,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { return new LoanScheduleData(currency, periods, loanTermInDays, cumulativePrincipalDisbursed, cumulativePrincipalDue, cumulativePrincipalPaid, cumulativePrincipalWrittenOff, cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeInterestPaid, cumulativeInterestWaived, cumulativeInterestWrittenOff, cumulativeInterestOutstanding, - cumulativeChargesToDate, cumulativeChargesPaid, cumulativeChargesOutstanding, + cumulativeFeeChargesToDate, cumulativeFeeChargesPaid, cumulativeFeeChargesOutstanding, + cumulativePenaltyChargesToDate, cumulativePenaltyChargesPaid, cumulativePenaltyChargesOutstanding, totalCostOfLoan, totalExpectedRepayment, totalPaidToDate, totalWaivedToDate, totalWrittenOffToDate, totalOutstanding, totalInArrears); } catch (EmptyResultDataAccessException e) { throw new LoanNotFoundException(loanId); @@ -516,10 +520,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { public String loanScheduleSchema() { - return " ls.loan_id as loanId, ls.installment as period, ls.duedate as dueDate, " + return " ls.loan_id as loanId, ls.installment as period, ls.fromdate as fromDate, 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.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 " + + " 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, " + + " ls.penalty_charges_amount as penaltyChargesDue, ls.penalty_charges_completed_derived as penaltyChargesPaid, ls.penalty_charges_waived_derived as penaltyChargesWaived, ls.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff " + " from m_loan l " + " join m_loan_repayment_schedule ls on ls.loan_id = l.id "; } @@ -529,6 +534,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Long loanId = rs.getLong("loanId"); final Integer period = JdbcSupport.getInteger(rs, "period"); + LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromDate"); final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate"); final BigDecimal principalDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDue"); final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid"); @@ -537,28 +543,43 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { // 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 = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "interestDue"); + final BigDecimal interestExpectedDue = 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 interestActualDue = interestExpectedDue.subtract(interestWaived).subtract(interestWrittenOff); + final BigDecimal interestOutstanding = interestActualDue.subtract(interestPaid); - final BigDecimal feeChargesDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "feeChargesDue"); + final BigDecimal feeChargesExpectedDue = 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 feeChargesActualDue = feeChargesExpectedDue.subtract(feeChargesWaived).subtract(feeChargesWrittenOff); + final BigDecimal feeChargesOutstanding = feeChargesActualDue.subtract(feeChargesPaid); + + final BigDecimal penaltyChargesExpectedDue = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesDue"); + final BigDecimal penaltyChargesPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesPaid"); + final BigDecimal penaltyChargesWaived = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWaived"); + final BigDecimal penaltyChargesWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "penaltyChargesWrittenOff"); + + final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived).subtract(penaltyChargesWrittenOff); + final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid); - final BigDecimal totalDueForPeriod = principalDue.add(interestDue).add(feeChargesDue); + final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(penaltyChargesExpectedDue); + + final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod); 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 totalActualCostOfLoanForPeriod = interestActualDue.add(feeChargesActualDue).add(penaltyChargesActualDue); + + if (fromDate == null) { + fromDate = this.lastDueDate; + } final BigDecimal outstandingPrincipleBalanceOfLoan = outstandingLoanPrincipalBalance.subtract(principalDue); // update based on current period values @@ -567,9 +588,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, principalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipleBalanceOfLoan, - interestDue, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, - feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, - totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, totalOutstandingForPeriod); + interestExpectedDue, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, + feeChargesExpectedDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, + penaltyChargesExpectedDue, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, + totalDueForPeriod, totalPaidForPeriod, totalWaivedForPeriod, totalWrittenOffForPeriod, totalOutstandingForPeriod, totalActualCostOfLoanForPeriod); } } diff --git a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanSchedulePeriodDataWrapper.java b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanSchedulePeriodDataWrapper.java index 5bd501c1f..5ae50693c 100644 --- a/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanSchedulePeriodDataWrapper.java +++ b/mifosng-provider/src/main/java/org/mifosng/platform/loan/service/LoanSchedulePeriodDataWrapper.java @@ -93,26 +93,50 @@ public class LoanSchedulePeriodDataWrapper { return cumulativeValue; } - public BigDecimal deriveCumulativeChargesToDate() { + public BigDecimal deriveCumulativeFeeChargesToDate() { BigDecimal cumulativeValue = BigDecimal.ZERO; for (LoanSchedulePeriodData period : this.periods) { - cumulativeValue = cumulativeValue.add(period.chargesDue()); + cumulativeValue = cumulativeValue.add(period.feeChargesDue()); } return cumulativeValue; } - public BigDecimal deriveCumulativeChargesPaid() { + public BigDecimal deriveCumulativeFeeChargesPaid() { BigDecimal cumulativeValue = BigDecimal.ZERO; for (LoanSchedulePeriodData period : this.periods) { - cumulativeValue = cumulativeValue.add(period.chargesPaid()); + cumulativeValue = cumulativeValue.add(period.feeChargesPaid()); } return cumulativeValue; } - public BigDecimal deriveCumulativeChargesOutstanding() { + public BigDecimal deriveCumulativeFeeChargesOutstanding() { BigDecimal cumulativeValue = BigDecimal.ZERO; for (LoanSchedulePeriodData period : this.periods) { - cumulativeValue = cumulativeValue.add(period.chargesOutstanding()); + cumulativeValue = cumulativeValue.add(period.feeChargesOutstanding()); + } + return cumulativeValue; + } + + public BigDecimal deriveCumulativePenaltyChargesToDate() { + BigDecimal cumulativeValue = BigDecimal.ZERO; + for (LoanSchedulePeriodData period : this.periods) { + cumulativeValue = cumulativeValue.add(period.penaltyChargesDue()); + } + return cumulativeValue; + } + + public BigDecimal deriveCumulativePenaltyChargesPaid() { + BigDecimal cumulativeValue = BigDecimal.ZERO; + for (LoanSchedulePeriodData period : this.periods) { + cumulativeValue = cumulativeValue.add(period.penaltyChargesPaid()); + } + return cumulativeValue; + } + + public BigDecimal deriveCumulativePenaltyChargesOutstanding() { + BigDecimal cumulativeValue = BigDecimal.ZERO; + for (LoanSchedulePeriodData period : this.periods) { + cumulativeValue = cumulativeValue.add(period.penaltyChargesOutstanding()); } return cumulativeValue; } 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 09c50f133..790c8a96b 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 @@ -381,11 +381,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf for (LoanSchedulePeriodData scheduledLoanPeriod : loanSchedule.getPeriods()) { if (scheduledLoanPeriod.isRepaymentPeriod()) { LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment( - loan, scheduledLoanPeriod.periodNumber(), + loan, + scheduledLoanPeriod.periodNumber(), + scheduledLoanPeriod.periodFromDate(), scheduledLoanPeriod.periodDueDate(), scheduledLoanPeriod.principalDue(), scheduledLoanPeriod.interestDue(), - scheduledLoanPeriod.chargesDue()); + scheduledLoanPeriod.feeChargesDue(), + scheduledLoanPeriod.penaltyChargesDue()); modifiedLoanRepaymentSchedule.add(installment); } @@ -628,7 +631,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override - public EntityIdentifier addLoanCharge(LoanChargeCommand command) { + public EntityIdentifier addLoanCharge(final LoanChargeCommand command) { this.context.authenticatedUser(); LoanChargeCommandValidator validator = new LoanChargeCommandValidator(command); 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 729fb0015..bab48ee7a 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 @@ -119,16 +119,18 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort outstandingBalance = outstandingBalance.minus(principalForInstallment); - Money chargesForInstallment = cumulativeChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency); - Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment).plus(chargesForInstallment); - cumulativeChargesToDate = cumulativeChargesToDate.add(chargesForInstallment.getAmount()); + final Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency); + final Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(startDate, scheduledDueDate, loanCharges, monetaryCurrency); + final Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment).plus(feeChargesForInstallment).plus(penaltyChargesForInstallment); + cumulativeChargesToDate = cumulativeChargesToDate.add(feeChargesForInstallment.getAmount()); LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate, scheduledDueDate, principalForInstallment.getAmount(), outstandingBalance.getAmount(), interestForInstallment.getAmount(), - chargesForInstallment.getAmount(), + feeChargesForInstallment.getAmount(), + penaltyChargesForInstallment.getAmount(), totalInstallmentDue.getAmount()); periods.add(installment); @@ -156,7 +158,7 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort cumulativePrincipalOutstanding, cumulativeInterestExpected, cumulativeChargesToDate, totalExpectedRepayment); } - private Money cumulativeChargesDueWithin( + private Money cumulativeFeeChargesDueWithin( final LocalDate periodStart, final LocalDate periodEnd, final Set loanCharges, @@ -165,7 +167,24 @@ public class EqualInstallmentsAmortizationLoanScheduleGenerator implements Amort Money cumulative = Money.zero(monetaryCurrency); for (LoanCharge loanCharge : loanCharges) { - if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd)) { + if (loanCharge.isDueForCollectionBetween(periodStart, periodEnd) && loanCharge.isFeeCharge()) { + cumulative = cumulative.plus(loanCharge.amount()); + } + } + + return cumulative; + } + + private Money cumulativePenaltyChargesDueWithin( + 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) && loanCharge.isPenaltyCharge()) { cumulative = cumulative.plus(loanCharge.amount()); } } 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 56a51b5e1..0cff46ca6 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 @@ -100,18 +100,18 @@ public class EqualPrincipalAmortizationLoanScheduleGenerator implements Amortiza 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()); + Money feeChargesForInstallment = Money.zero(monetaryCurrency); + Money penaltyChargesForInstallment = Money.zero(monetaryCurrency); + Money totalInstallmentDue = principalForInstallment.plus(interestForInstallment).plus(feeChargesForInstallment); + cumulativeChargesToDate = cumulativeChargesToDate.add(feeChargesForInstallment.getAmount()); LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate, scheduledDueDate, principalForInstallment.getAmount(), outstandingBalance.getAmount(), interestForInstallment.getAmount(), - chargesForInstallment.getAmount(), + feeChargesForInstallment.getAmount(), + penaltyChargesForInstallment.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 3b2f215f6..0f2b3b937 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 @@ -109,17 +109,19 @@ public class FlatMethodLoanScheduleGenerator implements LoanScheduleGenerator { 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()); + Money feeChargesForInstallment = Money.zero(monetaryCurrency); + Money penaltyChargesForInstallment = Money.zero(monetaryCurrency); + + Money totalInstallmentDue = principalPerInstallment.plus(interestPerInstallment).plus(feeChargesForInstallment).plus(penaltyChargesForInstallment); + cumulativeChargesToDate = cumulativeChargesToDate.add(feeChargesForInstallment.getAmount()).add(penaltyChargesForInstallment.getAmount()); LoanSchedulePeriodData installment = LoanSchedulePeriodData.repaymentOnlyPeriod(periodNumber, startDate, scheduledDueDate, principalPerInstallment.getAmount(), outstandingBalance.getAmount(), interestPerInstallment.getAmount(), - chargesForInstallment.getAmount(), + feeChargesForInstallment.getAmount(), + penaltyChargesForInstallment.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 b59ed56d7..7e4cb0b4b 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 @@ -8,11 +8,13 @@ public class LoanRepaymentScheduleInstallmentBuilder { private Loan loan = null; private Integer installmentNumber = Integer.valueOf(1); + private LocalDate fromDate = LocalDate.now(); private LocalDate dueDate = LocalDate.now(); 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 Money penaltyCharges = new MoneyBuilder().build(); private boolean completed = false; public LoanRepaymentScheduleInstallmentBuilder(MonetaryCurrency currencyDetail) { @@ -22,7 +24,7 @@ public class LoanRepaymentScheduleInstallmentBuilder { } public LoanRepaymentScheduleInstallment build() { - LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, installmentNumber, dueDate, principal.getAmount(), interest.getAmount(), feeCharges.getAmount()); + LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate, principal.getAmount(), interest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount()); if (completed) { installment.payPrincipalComponent(principal); installment.payInterestComponent(interest);