From c60f14885620072d758890fe28e2c31b58f9dc4f Mon Sep 17 00:00:00 2001 From: Adi Narayana Raju Date: Tue, 10 Nov 2015 15:25:08 +0530 Subject: [PATCH] Floating Rates Feature --- .../service/CommandWrapperBuilder.java | 16 + .../api/FloatingRatesApiResource.java | 129 ++++++ .../floatingrates/data/FloatingRateDTO.java | 60 +++ .../floatingrates/data/FloatingRateData.java | 132 ++++++ .../data/FloatingRatePeriodData.java | 123 ++++++ .../data/InterestRatePeriodData.java | 81 ++++ .../floatingrates/domain/FloatingRate.java | 269 ++++++++++++ .../domain/FloatingRatePeriod.java | 146 +++++++ .../domain/FloatingRateRepository.java | 19 + .../domain/FloatingRateRepositoryWrapper.java | 43 ++ .../FloatingRateNotFoundException.java | 21 + .../CreateFloatingRateCommandHandler.java | 35 ++ .../UpdateFloatingRateCommandHandler.java | 35 ++ .../FloatingRateDataValidator.java | 263 +++++++++++ .../FloatingRateWritePlatformService.java | 16 + .../FloatingRateWritePlatformServiceImpl.java | 124 ++++++ .../FloatingRatesReadPlatformService.java | 27 ++ .../FloatingRatesReadPlatformServiceImpl.java | 279 ++++++++++++ .../loanaccount/api/LoanApiConstants.java | 8 +- .../loanaccount/api/LoansApiResource.java | 18 +- .../loanaccount/data/LoanAccountData.java | 128 ++++-- .../data/LoanTermVariationsData.java | 42 ++ .../data/ScheduleGeneratorDTO.java | 15 +- .../portfolio/loanaccount/domain/Loan.java | 157 ++++++- .../domain/LoanAccountDomainServiceJpa.java | 28 +- .../loanaccount/domain/LoanRepository.java | 5 + .../domain/LoanTermVariationType.java | 10 +- .../data/LoanScheduleRecalculationDTO.java | 23 +- .../domain/AbstractLoanScheduleGenerator.java | 81 +++- ...gBalanceInterestLoanScheduleGenerator.java | 29 +- .../FlatInterestLoanScheduleGenerator.java | 4 +- .../domain/LoanApplicationTerms.java | 58 +-- .../domain/LoanScheduleGenerator.java | 3 +- .../service/LoanScheduleAssembler.java | 44 +- ...cheduleCalculationPlatformServiceImpl.java | 30 +- .../domain/DefaultLoanReschedulerFactory.java | 9 +- .../domain/LoanReschedulerFactory.java | 3 +- ...nReschedulePreviewPlatformServiceImpl.java | 37 +- ...heduleRequestWritePlatformServiceImpl.java | 55 ++- ...ateLoanScheduleQueryFromApiJsonHelper.java | 14 +- ...anApplicationCommandFromApiJsonHelper.java | 189 ++++++-- ...WritePlatformServiceJpaRepositoryImpl.java | 43 +- .../loanaccount/service/LoanAssembler.java | 10 +- .../service/LoanReadPlatformService.java | 5 +- .../service/LoanReadPlatformServiceImpl.java | 280 ++++++++---- .../service/LoanSchedularServiceImpl.java | 10 +- ...WritePlatformServiceJpaRepositoryImpl.java | 113 +++-- .../api/LoanProductsApiResource.java | 14 +- .../loanproduct/data/LoanProductData.java | 95 +++- .../loanproduct/domain/LoanProduct.java | 107 ++++- .../domain/LoanProductFloatingRates.java | 148 +++++++ .../domain/LoanProductMinMaxConstraints.java | 5 + .../domain/LoanProductRelatedDetail.java | 25 +- ...eModifiedDueToNonClosedLoansException.java | 15 + .../LoanProductDataValidator.java | 413 ++++++++++++++---- .../loanproduct/service/LoanEnumerations.java | 6 +- .../LoanProductReadPlatformServiceImpl.java | 27 +- ...WritePlatformServiceJpaRepositoryImpl.java | 42 +- .../core_db/V279__floating_rates.sql | 61 +++ 59 files changed, 3767 insertions(+), 460 deletions(-) create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/api/FloatingRatesApiResource.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRateDTO.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRateData.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRatePeriodData.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/InterestRatePeriodData.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRate.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRatePeriod.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRateRepository.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRateRepositoryWrapper.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/exception/FloatingRateNotFoundException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/handler/CreateFloatingRateCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/handler/UpdateFloatingRateCommandHandler.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/serialization/FloatingRateDataValidator.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRateWritePlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRatesReadPlatformService.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductFloatingRates.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/exception/LoanProductCannotBeModifiedDueToNonClosedLoansException.java create mode 100644 mifosng-provider/src/main/resources/sql/migrations/core_db/V279__floating_rates.sql diff --git a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java index cceb6f9de..32c3ec403 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/commands/service/CommandWrapperBuilder.java @@ -2529,4 +2529,20 @@ public class CommandWrapperBuilder { this.href = "/provisioningentries/"+entryId; return this; } + + public CommandWrapperBuilder createFloatingRate() { + this.actionName = "CREATE"; + this.entityName = "FLOATINGRATE"; + this.entityId = null; + this.href = "/floatingrates"; + return this; + } + + public CommandWrapperBuilder updateFloatingRate(final Long floatingRateId) { + this.actionName = "UPDATE"; + this.entityName = "FLOATINGRATE"; + this.entityId = floatingRateId; + this.href = "/floatingrates/"+floatingRateId; + return this; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/api/FloatingRatesApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/api/FloatingRatesApiResource.java new file mode 100644 index 000000000..e5abe3a15 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/api/FloatingRatesApiResource.java @@ -0,0 +1,129 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.mifosplatform.commands.domain.CommandWrapper; +import org.mifosplatform.commands.service.CommandWrapperBuilder; +import org.mifosplatform.commands.service.PortfolioCommandSourceWritePlatformService; +import org.mifosplatform.infrastructure.core.api.ApiRequestParameterHelper; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.mifosplatform.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/floatingrates") +@Component +@Scope("singleton") +public class FloatingRatesApiResource { + + private static final String RESOURCE_NAME = "FLOATINGRATE"; + private static final Set LIST_FLOATING_RATES_PARAMETERS = new HashSet<>( + Arrays.asList("id", "name", "isBaseLendingRate", "isActive", + "createdby", "createdOn", "modifiedBy", "modifiedOn")); + private static final Set INDIVIDUAL_FLOATING_RATES_PARAMETERS = new HashSet<>( + Arrays.asList("id", "name", "isBaseLendingRate", "isActive", + "createdBy", "createdOn", "modifiedBy", "modifiedOn", + "ratePeriods")); + private final PlatformSecurityContext context; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + + @Autowired + public FloatingRatesApiResource( + final PlatformSecurityContext context, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + final DefaultToApiJsonSerializer toApiJsonSerializer, + final ApiRequestParameterHelper apiRequestParameterHelper, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { + this.context = context; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String createFloatingRate(final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder() + .createFloatingRate().withJson(apiRequestBodyAsJson).build(); + final CommandProcessingResult result = this.commandsSourceWritePlatformService + .logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveAll(@Context final UriInfo uriInfo) { + this.context.authenticatedUser().validateHasReadPermission( + RESOURCE_NAME); + final List floatingRates = this.floatingRatesReadPlatformService + .retrieveAll(); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper + .process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, floatingRates, + FloatingRatesApiResource.LIST_FLOATING_RATES_PARAMETERS); + } + + @GET + @Path("{floatingRateId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveOne( + @PathParam("floatingRateId") final Long floatingRateId, + @Context final UriInfo uriInfo) { + this.context.authenticatedUser().validateHasReadPermission( + RESOURCE_NAME); + final FloatingRateData floatingRates = this.floatingRatesReadPlatformService + .retrieveOne(floatingRateId); + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper + .process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, floatingRates, + FloatingRatesApiResource.INDIVIDUAL_FLOATING_RATES_PARAMETERS); + } + + @PUT + @Path("{floatingRateId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String updateFloatingRate( + @PathParam("floatingRateId") final Long floatingRateId, + final String apiRequestBodyAsJson) { + final CommandWrapper commandRequest = new CommandWrapperBuilder() + .updateFloatingRate(floatingRateId) + .withJson(apiRequestBodyAsJson).build(); + final CommandProcessingResult result = this.commandsSourceWritePlatformService + .logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRateDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRateDTO.java new file mode 100644 index 000000000..68d710bb8 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRateDTO.java @@ -0,0 +1,60 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.data; + +import java.math.BigDecimal; +import java.util.Collection; + +import org.joda.time.LocalDate; + +public class FloatingRateDTO { + + private final boolean isFloatingInterestRate; + private final LocalDate startDate; + private BigDecimal interestRateDiff; + private final Collection baseLendingRatePeriods; + + public FloatingRateDTO(final boolean isFloatingInterestRate, + final LocalDate startDate, final BigDecimal interestRateDiff, + final Collection baseLendingRatePeriods) { + this.isFloatingInterestRate = isFloatingInterestRate; + this.startDate = startDate; + this.interestRateDiff = interestRateDiff; + this.baseLendingRatePeriods = baseLendingRatePeriods; + } + + public BigDecimal fetchBaseRate(LocalDate date) { + BigDecimal rate = null; + for (FloatingRatePeriodData periodData : this.baseLendingRatePeriods) { + if (date.isBefore(new LocalDate(periodData.getFromDate()))) { + break; + } + rate = periodData.getInterestRate(); + } + return rate; + } + + public void addInterestRateDiff(final BigDecimal diff) { + this.interestRateDiff = this.interestRateDiff.add(diff); + } + + public boolean isFloatingInterestRate() { + return this.isFloatingInterestRate; + } + + public LocalDate getStartDate() { + return this.startDate; + } + + public BigDecimal getInterestRateDiff() { + return this.interestRateDiff; + } + + public Collection getBaseLendingRatePeriods() { + return this.baseLendingRatePeriods; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRateData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRateData.java new file mode 100644 index 000000000..6adcd40b1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRateData.java @@ -0,0 +1,132 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.data; + +import java.util.Date; +import java.util.List; + +import org.apache.commons.lang.builder.CompareToBuilder; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.mifosplatform.infrastructure.core.data.EnumOptionData; + +public class FloatingRateData implements Comparable { + + private final Long id; + private final String name; + private final boolean isBaseLendingRate; + private final boolean isActive; + private final String createdBy; + private final Date createdOn; + private final String modifiedBy; + private final Date modifiedOn; + private final List ratePeriods; + @SuppressWarnings("unused") + private final List interestRateFrequencyTypeOptions; + + public FloatingRateData(Long id, String name, boolean isBaseLendingRate, + boolean isActive, String createdBy, Date createdOn, + String modifiedBy, Date modifiedOn, + List ratePeriods, + List interestRateFrequencyTypeOptions) { + this.id = id; + this.name = name; + this.isBaseLendingRate = isBaseLendingRate; + this.isActive = isActive; + this.createdBy = createdBy; + this.createdOn = createdOn; + this.modifiedBy = modifiedBy; + this.modifiedOn = modifiedOn; + this.ratePeriods = ratePeriods; + this.interestRateFrequencyTypeOptions = interestRateFrequencyTypeOptions; + } + + public Long getId() { + return this.id; + } + + public String getName() { + return this.name; + } + + public boolean isBaseLendingRate() { + return this.isBaseLendingRate; + } + + public boolean isActive() { + return this.isActive; + } + + public String getCreatedBy() { + return this.createdBy; + } + + public Date getCreatedOn() { + return this.createdOn; + } + + public String getModifiedBy() { + return this.modifiedBy; + } + + public Date getModifiedOn() { + return this.modifiedOn; + } + + public List getRatePeriods() { + return this.ratePeriods; + } + + @Override + public int compareTo(final FloatingRateData obj) { + if (obj == null) { + return -1; + } + return new CompareToBuilder() // + .append(this.id, obj.id) // + .append(this.name, obj.name) // + .append(this.isBaseLendingRate, obj.isBaseLendingRate) // + .append(this.isActive, obj.isActive) // + .toComparison(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final FloatingRateData rhs = (FloatingRateData) obj; + return new EqualsBuilder() // + .append(this.id, rhs.id) // + .append(this.name, rhs.name) // + .append(this.isBaseLendingRate, rhs.isBaseLendingRate) // + .append(this.isActive, rhs.isActive) // + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) // + .append(this.id) // + .append(this.name) // + .append(this.isBaseLendingRate) // + .append(this.isActive) // + .toHashCode(); + } + + public static FloatingRateData toTemplate( + List interestRateFrequencyTypeOptions) { + // TODO Auto-generated method stub + return new FloatingRateData(null, null, false, true, null, null, null, + null, null, interestRateFrequencyTypeOptions); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRatePeriodData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRatePeriodData.java new file mode 100644 index 000000000..ac997011e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/FloatingRatePeriodData.java @@ -0,0 +1,123 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.data; + +import java.math.BigDecimal; +import java.util.Date; + +import org.apache.commons.lang.builder.CompareToBuilder; +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.joda.time.LocalDate; + +public class FloatingRatePeriodData implements + Comparable { + + private Long id; + private Date fromDate; + private BigDecimal interestRate; + private boolean isDifferentialToBaseLendingRate; + private boolean isActive; + private String createdBy; + private Date createdOn; + private String modifiedBy; + private Date modifiedOn; + + public FloatingRatePeriodData(Long id, Date fromDate, + BigDecimal interestRate, boolean isDifferentialToBaseLendingRate, + boolean isActive, String createdBy, Date createdOn, + String modifiedBy, Date modifiedOn) { + this.id = id; + this.fromDate = fromDate; + this.interestRate = interestRate; + this.isDifferentialToBaseLendingRate = isDifferentialToBaseLendingRate; + this.isActive = isActive; + this.createdBy = createdBy; + this.createdOn = createdOn; + this.modifiedBy = modifiedBy; + this.modifiedOn = modifiedOn; + } + + public Long getId() { + return this.id; + } + + public Date getFromDate() { + return this.fromDate; + } + + public LocalDate getFromDateAsLocalDate() { + return new LocalDate(this.fromDate); + } + + public BigDecimal getInterestRate() { + return this.interestRate; + } + + public boolean isDifferentialToBaseLendingRate() { + return this.isDifferentialToBaseLendingRate; + } + + public boolean isActive() { + return this.isActive; + } + + public String getCreatedBy() { + return this.createdBy; + } + + public Date getCreatedOn() { + return this.createdOn; + } + + public String getModifiedBy() { + return this.modifiedBy; + } + + public Date getModifiedOn() { + return this.modifiedOn; + } + + @Override + public int compareTo(final FloatingRatePeriodData obj) { + if (obj == null) { + return -1; + } + return new CompareToBuilder() // + .append(this.id, obj.id) // + .append(this.fromDate, obj.fromDate) // + .append(this.isActive, obj.isActive) // + .toComparison(); + } + + @Override + public boolean equals(final Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj.getClass() != getClass()) { + return false; + } + final FloatingRatePeriodData rhs = (FloatingRatePeriodData) obj; + return new EqualsBuilder() // + .append(this.id, rhs.id) // + .append(this.fromDate, rhs.fromDate) // + .append(this.isActive, rhs.isActive) // + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) // + .append(this.id) // + .append(this.fromDate) // + .append(this.isActive) // + .toHashCode(); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/InterestRatePeriodData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/InterestRatePeriodData.java new file mode 100644 index 000000000..de409db5c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/data/InterestRatePeriodData.java @@ -0,0 +1,81 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.data; + +import java.math.BigDecimal; +import java.util.Date; + +public class InterestRatePeriodData { + + private Date fromDate; + private final BigDecimal interestRate; + private final boolean isDifferentialToBLR; + private final Date blrFromDate; + private final BigDecimal blrInterestRate; + private BigDecimal loanDifferentialInterestRate; + private BigDecimal loanProductDifferentialInterestRate; + private BigDecimal effectiveInterestRate; + + public InterestRatePeriodData(Date fromDate, BigDecimal interestRate, + boolean isDifferentialToBLR, Date blrFromDate, + BigDecimal blrInterestRate) { + this.fromDate = fromDate; + this.interestRate = interestRate; + this.isDifferentialToBLR = isDifferentialToBLR; + this.blrFromDate = blrFromDate; + this.blrInterestRate = blrInterestRate; + } + + public Date getFromDate() { + return this.fromDate; + } + + public void setFromDate(Date fromDate) { + this.fromDate = fromDate; + } + + public BigDecimal getInterestRate() { + return this.interestRate; + } + + public boolean isDifferentialToBLR() { + return this.isDifferentialToBLR; + } + + public Date getBlrFromDate() { + return this.blrFromDate; + } + + public BigDecimal getBlrInterestRate() { + return this.blrInterestRate; + } + + public BigDecimal getLoanDifferentialInterestRate() { + return this.loanDifferentialInterestRate; + } + + public void setLoanDifferentialInterestRate( + BigDecimal loanDifferentialInterestRate) { + this.loanDifferentialInterestRate = loanDifferentialInterestRate; + } + + public BigDecimal getLoanProductDifferentialInterestRate() { + return this.loanProductDifferentialInterestRate; + } + + public void setLoanProductDifferentialInterestRate( + BigDecimal loanProductDifferentialInterestRate) { + this.loanProductDifferentialInterestRate = loanProductDifferentialInterestRate; + } + + public BigDecimal getEffectiveInterestRate() { + return this.effectiveInterestRate; + } + + public void setEffectiveInterestRate(BigDecimal effectiveInterestRate) { + this.effectiveInterestRate = effectiveInterestRate; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRate.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRate.java new file mode 100644 index 000000000..cfb87915a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRate.java @@ -0,0 +1,269 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.domain; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.hibernate.annotations.LazyCollection; +import org.hibernate.annotations.LazyCollectionOption; +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.serialization.JsonParserHelper; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.useradministration.domain.AppUser; +import org.springframework.data.jpa.domain.AbstractPersistable; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +@Entity +@Table(name = "m_floating_rates", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") }) +public class FloatingRate extends AbstractPersistable { + + @Column(name = "name", length = 200, unique = true, nullable = false) + private String name; + + @Column(name = "is_base_lending_rate", nullable = false) + private boolean isBaseLendingRate; + + @Column(name = "is_active", nullable = false) + private boolean isActive; + + @OrderBy(value = "fromDate,id") + @LazyCollection(LazyCollectionOption.FALSE) + @OneToMany(cascade = CascadeType.ALL, mappedBy = "floatingRate", orphanRemoval = true) + private Set floatingRatePeriods; + + @ManyToOne(optional = true) + @JoinColumn(name = "createdby_id", nullable = false) + private AppUser createdBy; + + @ManyToOne(optional = true) + @JoinColumn(name = "lastmodifiedby_id", nullable = false) + private AppUser modifiedBy; + + @Column(name = "created_date", nullable = false) + private Date createdOn; + + @Column(name = "lastmodified_date", nullable = false) + private Date modifiedOn; + + public FloatingRate() { + + } + + public FloatingRate(String name, boolean isBaseLendingRate, + boolean isActive, Set floatingRatePeriods, + AppUser createdBy, AppUser modifiedBy, Date createdOn, + Date modifiedOn) { + this.name = name; + this.isBaseLendingRate = isBaseLendingRate; + this.isActive = isActive; + this.floatingRatePeriods = floatingRatePeriods; + this.createdBy = createdBy; + this.createdOn = createdOn; + this.modifiedBy = modifiedBy; + this.modifiedOn = modifiedOn; + if (floatingRatePeriods != null) { + for (FloatingRatePeriod ratePeriod : floatingRatePeriods) { + ratePeriod.updateFloatingRate(this); + } + } + } + + public static FloatingRate createNew(AppUser currentUser, + JsonCommand command) { + + final String name = command.stringValueOfParameterNamed("name"); + final boolean isBaseLendingRate = command + .parameterExists("isBaseLendingRate") ? command + .booleanPrimitiveValueOfParameterNamed("isBaseLendingRate") + : false; + final boolean isActive = command.parameterExists("isActive") ? command + .booleanPrimitiveValueOfParameterNamed("isActive") : true; + final Set floatingRatePeriods = getRatePeriods( + currentUser, command); + final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); + + return new FloatingRate(name, isBaseLendingRate, isActive, + floatingRatePeriods, currentUser, currentUser, + currentDate.toDate(), currentDate.toDate()); + } + + private static Set getRatePeriods( + final AppUser currentUser, final JsonCommand command) { + if (!command.parameterExists("ratePeriods")) { + return null; + } + Set ratePeriods = new HashSet<>(); + JsonArray arrayOfParameterNamed = command + .arrayOfParameterNamed("ratePeriods"); + for (final JsonElement ratePeriod : arrayOfParameterNamed) { + final JsonObject ratePeriodObject = ratePeriod.getAsJsonObject(); + final JsonParserHelper helper = new JsonParserHelper(); + final Date fromDate = helper.extractLocalDateNamed("fromDate", + ratePeriod, new HashSet()).toDate(); + final BigDecimal interestRate = ratePeriodObject + .get("interestRate").getAsBigDecimal(); + final boolean isDifferentialToBaseLendingRate = helper + .parameterExists("isDifferentialToBaseLendingRate", + ratePeriod) ? ratePeriodObject.get( + "isDifferentialToBaseLendingRate").getAsBoolean() : false; + final boolean isActive = true; + final Date currentDate = DateUtils.getDateOfTenant(); + ratePeriods.add(new FloatingRatePeriod(fromDate, interestRate, + isDifferentialToBaseLendingRate, isActive, currentUser, + currentUser, currentDate, currentDate)); + } + + return ratePeriods; + } + + public String getName() { + return this.name; + } + + public boolean isBaseLendingRate() { + return this.isBaseLendingRate; + } + + public boolean isActive() { + return this.isActive; + } + + public Set getFloatingRatePeriods() { + return this.floatingRatePeriods; + } + + public AppUser getCreatedBy() { + return this.createdBy; + } + + public AppUser getModifiedBy() { + return this.modifiedBy; + } + + public Date getCreatedOn() { + return this.createdOn; + } + + public Date getModifiedOn() { + return this.modifiedOn; + } + + public Map update(final JsonCommand command, + final AppUser appUser) { + + final Map actualChanges = new LinkedHashMap<>(9); + + if (command.isChangeInStringParameterNamed("name", this.name)) { + final String newValue = command.stringValueOfParameterNamed("name"); + actualChanges.put("name", newValue); + this.name = newValue; + } + + if (command.isChangeInBooleanParameterNamed("isBaseLendingRate", + this.isBaseLendingRate)) { + final boolean newValue = command + .booleanPrimitiveValueOfParameterNamed("isBaseLendingRate"); + actualChanges.put("isBaseLendingRate", newValue); + this.isBaseLendingRate = newValue; + } + + if (command.isChangeInBooleanParameterNamed("isActive", this.isActive)) { + final boolean newValue = command + .booleanPrimitiveValueOfParameterNamed("isActive"); + actualChanges.put("isActive", newValue); + this.isActive = newValue; + } + + final Set newRatePeriods = getRatePeriods(appUser, + command); + if (newRatePeriods != null && !newRatePeriods.isEmpty()) { + updateRatePeriods(newRatePeriods, appUser); + actualChanges.put("ratePeriods", + command.jsonFragment("ratePeriods")); + } + + return actualChanges; + } + + private void updateRatePeriods( + final Set newRatePeriods, final AppUser appUser) { + final LocalDate today = DateUtils.getLocalDateOfTenant(); + if (this.floatingRatePeriods != null) { + for (FloatingRatePeriod ratePeriod : this.floatingRatePeriods) { + LocalDate fromDate = LocalDate.fromDateFields(ratePeriod + .getFromDate()); + if (fromDate.isAfter(today)) { + ratePeriod.setActive(false); + ratePeriod.setModifiedBy(appUser); + ratePeriod.setModifiedOn(today.toDate()); + } + } + } + for (FloatingRatePeriod newRatePeriod : newRatePeriods) { + newRatePeriod.updateFloatingRate(this); + this.floatingRatePeriods.add(newRatePeriod); + } + } + + public Collection fetchInterestRates( + final FloatingRateDTO floatingRateDTO) { + Collection applicableRates = new ArrayList<>(); + FloatingRatePeriod previousPeriod = null; + boolean addPeriodData = false; + for (FloatingRatePeriod floatingRatePeriod : this.floatingRatePeriods) { + if (floatingRatePeriod.isActive()) { + // will enter + if (applicableRates.isEmpty() + && floatingRateDTO.getStartDate().isBefore( + floatingRatePeriod.fetchFromDate())) { + if (floatingRateDTO.isFloatingInterestRate()) { + addPeriodData = true; + } + if (previousPeriod != null) { + applicableRates.add(previousPeriod + .toData(floatingRateDTO)); + } else if (!addPeriodData) { + applicableRates.add(floatingRatePeriod + .toData(floatingRateDTO)); + } + } + if (addPeriodData) { + applicableRates.add(floatingRatePeriod + .toData(floatingRateDTO)); + } + previousPeriod = floatingRatePeriod; + } + } + if (applicableRates.isEmpty() && previousPeriod != null) { + applicableRates.add(previousPeriod.toData(floatingRateDTO)); + } + return applicableRates; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRatePeriod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRatePeriod.java new file mode 100644 index 000000000..bfd721e5d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRatePeriod.java @@ -0,0 +1,146 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.domain; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.joda.time.LocalDate; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.useradministration.domain.AppUser; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_floating_rates_periods") +public class FloatingRatePeriod extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "floating_rates_id", nullable = false) + private FloatingRate floatingRate; + + @Column(name = "from_date", nullable = false) + private Date fromDate; + + @Column(name = "interest_rate", scale = 6, precision = 19, nullable = false) + private BigDecimal interestRate; + + @Column(name = "is_differential_to_base_lending_rate", nullable = false) + private boolean isDifferentialToBaseLendingRate; + + @Column(name = "is_active", nullable = false) + private boolean isActive; + + @ManyToOne(optional = true) + @JoinColumn(name = "createdby_id", nullable = false) + private AppUser createdBy; + + @ManyToOne(optional = true) + @JoinColumn(name = "lastmodifiedby_id", nullable = false) + private AppUser modifiedBy; + + @Column(name = "created_date", nullable = false) + private Date createdOn; + + @Column(name = "lastmodified_date", nullable = false) + private Date modifiedOn; + + public FloatingRatePeriod() { + + } + + public FloatingRatePeriod(Date fromDate, BigDecimal interestRate, + boolean isDifferentialToBaseLendingRate, boolean isActive, + AppUser createdBy, AppUser modifiedBy, Date createdOn, + Date modifiedOn) { + this.fromDate = fromDate; + this.interestRate = interestRate; + this.isDifferentialToBaseLendingRate = isDifferentialToBaseLendingRate; + this.isActive = isActive; + this.createdBy = createdBy; + this.modifiedBy = modifiedBy; + this.createdOn = createdOn; + this.modifiedOn = modifiedOn; + } + + public void updateFloatingRate(FloatingRate floatingRate) { + this.floatingRate = floatingRate; + } + + public FloatingRate getFloatingRatesId() { + return this.floatingRate; + } + + public Date getFromDate() { + return this.fromDate; + } + + public BigDecimal getInterestRate() { + return this.interestRate; + } + + public boolean isDifferentialToBaseLendingRate() { + return this.isDifferentialToBaseLendingRate; + } + + public boolean isActive() { + return this.isActive; + } + + public AppUser getCreatedBy() { + return this.createdBy; + } + + public AppUser getModifiedBy() { + return this.modifiedBy; + } + + public Date getCreatedOn() { + return this.createdOn; + } + + public Date getModifiedOn() { + return this.modifiedOn; + } + + public void setModifiedBy(AppUser modifiedBy) { + this.modifiedBy = modifiedBy; + } + + public void setModifiedOn(Date modifiedOn) { + this.modifiedOn = modifiedOn; + } + + public void setActive(boolean b) { + this.isActive = b; + } + + public LocalDate fetchFromDate() { + return new LocalDate(this.fromDate); + } + + public FloatingRatePeriodData toData(final FloatingRateDTO floatingRateDTO) { + + BigDecimal interest = getInterestRate().add( + floatingRateDTO.getInterestRateDiff()); + if (isDifferentialToBaseLendingRate()) { + interest = interest.add(floatingRateDTO + .fetchBaseRate(fetchFromDate())); + } + + return new FloatingRatePeriodData(getId(), getFromDate(), interest, + isDifferentialToBaseLendingRate(), isActive(), getCreatedBy() + .getUsername(), getCreatedOn(), getModifiedBy() + .getUsername(), getModifiedOn()); + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRateRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRateRepository.java new file mode 100644 index 000000000..476e4dec5 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRateRepository.java @@ -0,0 +1,19 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Query; + +public interface FloatingRateRepository extends + JpaRepository, + JpaSpecificationExecutor { + + @Query("from FloatingRate floatingRate where floatingRate.isBaseLendingRate = 1 and floatingRate.isActive = 1") + FloatingRate retrieveBaseLendingRate(); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRateRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRateRepositoryWrapper.java new file mode 100644 index 000000000..b8bdad00a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/domain/FloatingRateRepositoryWrapper.java @@ -0,0 +1,43 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.domain; + +import org.mifosplatform.portfolio.floatingrates.exception.FloatingRateNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class FloatingRateRepositoryWrapper { + + private final FloatingRateRepository floatingRateRepository; + + @Autowired + public FloatingRateRepositoryWrapper( + final FloatingRateRepository floatingRateRepository) { + this.floatingRateRepository = floatingRateRepository; + } + + public FloatingRate retrieveBaseLendingRate() { + return this.floatingRateRepository.retrieveBaseLendingRate(); + } + + public FloatingRate findOneWithNotFoundDetection(final Long id) { + final FloatingRate floatingRate = this.floatingRateRepository + .findOne(id); + if (floatingRate == null) { + throw new FloatingRateNotFoundException(id); + } + return floatingRate; + } + + public void save(FloatingRate newFloatingRate) { + this.floatingRateRepository.save(newFloatingRate); + } + + public void saveAndFlush(FloatingRate floatingRateForUpdate) { + this.floatingRateRepository.saveAndFlush(floatingRateForUpdate); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/exception/FloatingRateNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/exception/FloatingRateNotFoundException.java new file mode 100644 index 000000000..07f211c6c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/exception/FloatingRateNotFoundException.java @@ -0,0 +1,21 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class FloatingRateNotFoundException extends + AbstractPlatformResourceNotFoundException { + + public FloatingRateNotFoundException(final Long id) { + super("error.msg.floatingrate.id.invalid", + "Floating Rate with identifier " + id + " does not exist", id); + } + + public FloatingRateNotFoundException(final String globalisationMessageCode) { + super(globalisationMessageCode, "Floating Rate does not exist"); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/handler/CreateFloatingRateCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/handler/CreateFloatingRateCommandHandler.java new file mode 100644 index 000000000..5a2e1419e --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/handler/CreateFloatingRateCommandHandler.java @@ -0,0 +1,35 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.handler; + +import org.mifosplatform.commands.annotation.CommandType; +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRateWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "FLOATINGRATE", action = "CREATE") +public class CreateFloatingRateCommandHandler implements + NewCommandSourceHandler { + + private final FloatingRateWritePlatformService writePlatformService; + + @Autowired + public CreateFloatingRateCommandHandler( + final FloatingRateWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.createFloatingRate(command); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/handler/UpdateFloatingRateCommandHandler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/handler/UpdateFloatingRateCommandHandler.java new file mode 100644 index 000000000..75c6f7ecf --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/handler/UpdateFloatingRateCommandHandler.java @@ -0,0 +1,35 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.handler; + +import org.mifosplatform.commands.annotation.CommandType; +import org.mifosplatform.commands.handler.NewCommandSourceHandler; +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRateWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@CommandType(entity = "FLOATINGRATE", action = "UPDATE") +public class UpdateFloatingRateCommandHandler implements + NewCommandSourceHandler { + + private final FloatingRateWritePlatformService writePlatformService; + + @Autowired + public UpdateFloatingRateCommandHandler( + final FloatingRateWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.updateFloatingRate(command); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/serialization/FloatingRateDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/serialization/FloatingRateDataValidator.java new file mode 100644 index 000000000..6e74d1f23 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/serialization/FloatingRateDataValidator.java @@ -0,0 +1,263 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.serialization; + +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.joda.time.LocalDate; +import org.mifosplatform.infrastructure.core.data.ApiParameterError; +import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; +import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.core.service.DateUtils; +import org.mifosplatform.portfolio.floatingrates.domain.FloatingRate; +import org.mifosplatform.portfolio.floatingrates.domain.FloatingRateRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +@Component +public class FloatingRateDataValidator { + + private final Set supportedParametersForFloatingRates = new HashSet<>( + Arrays.asList("name", "isBaseLendingRate", "isActive", + "ratePeriods")); + private final Set supportedParametersForFloatingRatePeriods = new HashSet<>( + Arrays.asList("fromDate", "interestRate", + "isDifferentialToBaseLendingRate", "locale", "dateFormat")); + + private final FromJsonHelper fromApiJsonHelper; + private final FloatingRateRepository floatingRateRepository; + + @Autowired + public FloatingRateDataValidator(final FromJsonHelper fromApiJsonHelper, + final FloatingRateRepository floatingRateRepository) { + this.fromApiJsonHelper = fromApiJsonHelper; + this.floatingRateRepository = floatingRateRepository; + } + + public void validateForCreate(String json) { + final Type typeOfMap = new TypeToken>() { + }.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + this.supportedParametersForFloatingRates); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors).resource("floatingrate"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final String name = this.fromApiJsonHelper.extractStringNamed("name", + element); + baseDataValidator.reset().parameter("name").value(name).notBlank() + .notExceedingLengthOf(200); + + if (this.fromApiJsonHelper + .parameterExists("isBaseLendingRate", element)) { + final Boolean isBaseLendingRate = this.fromApiJsonHelper + .extractBooleanNamed("isBaseLendingRate", element); + + baseDataValidator.reset().parameter("isBaseLendingRate") + .value(isBaseLendingRate).notNull(); + if (isBaseLendingRate == null) { + baseDataValidator.reset().parameter("isBaseLendingRate") + .trueOrFalseRequired(false); + } else if (isBaseLendingRate) { + FloatingRate baseLendingRate = this.floatingRateRepository + .retrieveBaseLendingRate(); + if (baseLendingRate != null) { + baseDataValidator + .reset() + .parameter("isBaseLendingRate") + .value(isBaseLendingRate) + .failWithCode("baselendingrate.duplicate", + "Base Lending Rate already exists"); + } + } + } + + if (this.fromApiJsonHelper.parameterExists("isActive", element)) { + final Boolean isActive = this.fromApiJsonHelper + .extractBooleanNamed("isActive", element); + if (isActive == null) { + baseDataValidator.reset().parameter("isActive") + .trueOrFalseRequired(false); + } + } + + validateRatePeriods(baseDataValidator, element); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void validateRatePeriods(DataValidatorBuilder baseDataValidator, + JsonElement element) { + if (this.fromApiJsonHelper.parameterExists("ratePeriods", element)) { + final JsonArray ratePeriods = this.fromApiJsonHelper + .extractJsonArrayNamed("ratePeriods", element); + baseDataValidator.reset().parameter("ratePeriods") + .value(ratePeriods).notBlank().jsonArrayNotEmpty(); + + if (ratePeriods != null) { + List fromDates = new ArrayList<>(); + for (int i = 0; i < ratePeriods.size(); i++) { + final JsonElement ratePeriod = ratePeriods.get(i); + + this.fromApiJsonHelper.checkForUnsupportedParameters( + ratePeriod.getAsJsonObject(), + this.supportedParametersForFloatingRatePeriods); + + final LocalDate fromDate = this.fromApiJsonHelper + .extractLocalDateNamed("fromDate", ratePeriod); + baseDataValidator + .reset() + .parameter("fromDate") + .parameterAtIndexArray("fromDate", i + 1) + .value(fromDate) + .notBlank() + .validateDateAfter( + DateUtils.getLocalDateOfTenant() + .plusDays(1)); + if (fromDate != null) { + fromDates.add(fromDate); + } + + final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed("interestRate", + ratePeriod); + baseDataValidator.reset().parameter("interestRate") + .parameterAtIndexArray("interestRate", i + 1) + .value(interestRatePerPeriod).notNull() + .zeroOrPositiveAmount(); + + if (this.fromApiJsonHelper.parameterExists( + "isDifferentialToBaseLendingRate", ratePeriod)) { + final Boolean isDifferentialToBaseLendingRate = this.fromApiJsonHelper + .extractBooleanNamed( + "isDifferentialToBaseLendingRate", + ratePeriod); + if (isDifferentialToBaseLendingRate == null) { + baseDataValidator + .reset() + .parameter( + "isDifferentialToBaseLendingRate") + .parameterAtIndexArray( + "isDifferentialToBaseLendingRate", + i + 1).trueOrFalseRequired(false); + } else if (isDifferentialToBaseLendingRate) { + FloatingRate baseLendingRate = this.floatingRateRepository + .retrieveBaseLendingRate(); + if (baseLendingRate == null) { + baseDataValidator + .reset() + .parameter( + "isDifferentialToBaseLendingRate") + .parameterAtIndexArray( + "isDifferentialToBaseLendingRate", + i + 1) + .value(isDifferentialToBaseLendingRate) + .failWithCode( + "no.baselending.rate.defined", + "Base Lending Rate doesn't exists"); + } + + } + } + } + Set uniqueFromDates = new HashSet<>(fromDates); + if (fromDates.size() != uniqueFromDates.size()) { + baseDataValidator + .reset() + .parameter("fromDate") + .failWithCode("multiple.same.date", + "More than one entry in ratePeriods have same fromDate."); + } + + } + } + } + + public void validateForUpdate(String json, + FloatingRate floatingRateForUpdate) { + final Type typeOfMap = new TypeToken>() { + }.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + this.supportedParametersForFloatingRates); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors).resource("floatingrate"); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + if (this.fromApiJsonHelper.parameterExists("name", element)) { + final String name = this.fromApiJsonHelper.extractStringNamed( + "name", element); + baseDataValidator.reset().parameter("name").value(name).notBlank() + .notExceedingLengthOf(200); + } + + if (this.fromApiJsonHelper + .parameterExists("isBaseLendingRate", element)) { + final Boolean isBaseLendingRate = this.fromApiJsonHelper + .extractBooleanNamed("isBaseLendingRate", element); + + baseDataValidator.reset().parameter("isBaseLendingRate") + .value(isBaseLendingRate).notNull(); + if (isBaseLendingRate == null) { + baseDataValidator.reset().parameter("isBaseLendingRate") + .trueOrFalseRequired(false); + } else if (isBaseLendingRate) { + FloatingRate baseLendingRate = this.floatingRateRepository + .retrieveBaseLendingRate(); + if (baseLendingRate != null + && baseLendingRate.getId() != floatingRateForUpdate + .getId()) { + baseDataValidator + .reset() + .parameter("isBaseLendingRate") + .value(isBaseLendingRate) + .failWithCode("baselendingrate.duplicate", + "Base Lending Rate already exists"); + } + } + } + + if (this.fromApiJsonHelper.parameterExists("isActive", element)) { + final Boolean isActive = this.fromApiJsonHelper + .extractBooleanNamed("isActive", element); + if (isActive == null) { + baseDataValidator.reset().parameter("isActive") + .trueOrFalseRequired(false); + } + } + + validateRatePeriods(baseDataValidator, element); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist( + final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException( + "validation.msg.validation.errors.exist", + "Validation errors exist.", dataValidationErrors); + } + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRateWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRateWritePlatformService.java new file mode 100644 index 000000000..c22d43cf6 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRateWritePlatformService.java @@ -0,0 +1,16 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.service; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; + +public interface FloatingRateWritePlatformService { + + public CommandProcessingResult createFloatingRate(JsonCommand command); + + public CommandProcessingResult updateFloatingRate(JsonCommand command); +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java new file mode 100644 index 000000000..5bc442f58 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRateWritePlatformServiceImpl.java @@ -0,0 +1,124 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.service; + +import java.util.Map; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResult; +import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; +import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException; +import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; +import org.mifosplatform.portfolio.floatingrates.domain.FloatingRate; +import org.mifosplatform.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper; +import org.mifosplatform.portfolio.floatingrates.serialization.FloatingRateDataValidator; +import org.mifosplatform.useradministration.domain.AppUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class FloatingRateWritePlatformServiceImpl implements + FloatingRateWritePlatformService { + + private final static Logger logger = LoggerFactory + .getLogger(FloatingRateWritePlatformServiceImpl.class); + private final PlatformSecurityContext context; + private final FloatingRateDataValidator fromApiJsonDeserializer; + private final FloatingRateRepositoryWrapper floatingRateRepository; + + @Autowired + public FloatingRateWritePlatformServiceImpl( + final PlatformSecurityContext context, + final FloatingRateDataValidator fromApiJsonDeserializer, + final FloatingRateRepositoryWrapper floatingRateRepository) { + this.context = context; + this.fromApiJsonDeserializer = fromApiJsonDeserializer; + this.floatingRateRepository = floatingRateRepository; + } + + @Transactional + @Override + public CommandProcessingResult createFloatingRate(final JsonCommand command) { + try { + this.fromApiJsonDeserializer.validateForCreate(command.json()); + final AppUser currentUser = this.context.authenticatedUser(); + final FloatingRate newFloatingRate = FloatingRate.createNew( + currentUser, command); + this.floatingRateRepository.save(newFloatingRate); + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(newFloatingRate.getId()) // + .build(); + } catch (final DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } + } + + @Transactional + @Override + public CommandProcessingResult updateFloatingRate(final JsonCommand command) { + try { + final FloatingRate floatingRateForUpdate = this.floatingRateRepository + .findOneWithNotFoundDetection(command.entityId()); + this.fromApiJsonDeserializer.validateForUpdate(command.json(), + floatingRateForUpdate); + final AppUser currentUser = this.context.authenticatedUser(); + final Map changes = floatingRateForUpdate.update( + command, currentUser); + + if (!changes.isEmpty()) { + this.floatingRateRepository.save(floatingRateForUpdate); + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(command.entityId()) // + .with(changes) // + .build(); + } catch (final DataIntegrityViolationException dve) { + handleDataIntegrityIssues(command, dve); + return CommandProcessingResult.empty(); + } + } + + private void handleDataIntegrityIssues(final JsonCommand command, + final DataIntegrityViolationException dve) { + final Throwable realCause = dve.getMostSpecificCause(); + + if (realCause.getMessage().contains("unq_name")) { + + final String name = command.stringValueOfParameterNamed("name"); + throw new PlatformDataIntegrityException( + "error.msg.floatingrates.duplicate.name", + "Floating Rate with name `" + name + "` already exists", + "name", name); + } + + if (realCause.getMessage().contains("unq_rate_period")) { + throw new PlatformDataIntegrityException( + "error.msg.floatingrates.duplicate.active.fromdate", + "Attempt to add multiple floating rate periods with same fromdate", + "fromdate", ""); + } + + logAsErrorUnexpectedDataIntegrityException(dve); + throw new PlatformDataIntegrityException( + "error.msg.floatingrates.unknown.data.integrity.issue", + "Unknown data integrity issue with resource."); + } + + private void logAsErrorUnexpectedDataIntegrityException( + DataIntegrityViolationException dve) { + logger.error(dve.getMessage(), dve); + + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRatesReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRatesReadPlatformService.java new file mode 100644 index 000000000..929c6c283 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRatesReadPlatformService.java @@ -0,0 +1,27 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.service; + +import java.util.List; + +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateData; +import org.mifosplatform.portfolio.floatingrates.data.InterestRatePeriodData; + +public interface FloatingRatesReadPlatformService { + + List retrieveAll(); + + List retrieveLookupActive(); + + FloatingRateData retrieveOne(Long floatingRateId); + + List retrieveAllActive(); + + FloatingRateData retrieveBaseLendingRate(); + + List retrieveInterestRatePeriods(Long floatingRateId); + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java new file mode 100644 index 000000000..6e125b85a --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/floatingrates/service/FloatingRatesReadPlatformServiceImpl.java @@ -0,0 +1,279 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.floatingrates.service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +import org.mifosplatform.infrastructure.core.service.RoutingDataSource; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateData; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.data.InterestRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.exception.FloatingRateNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class FloatingRatesReadPlatformServiceImpl implements + FloatingRatesReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + + @Autowired + public FloatingRatesReadPlatformServiceImpl( + final RoutingDataSource dataSource) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public List retrieveAll() { + FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(false); + final String sql = "select " + rateMapper.schema(); + return this.jdbcTemplate.query(sql, rateMapper); + } + + @Override + public List retrieveAllActive() { + FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(false); + final String sql = "select " + rateMapper.schema() + + " where rate.is_active = 1 "; + return this.jdbcTemplate.query(sql, rateMapper); + } + + @Override + public List retrieveLookupActive() { + FloatingRateLookupMapper rateMapper = new FloatingRateLookupMapper(); + final String sql = "select " + rateMapper.schema() + + " where rate.is_active = 1 "; + return this.jdbcTemplate.query(sql, rateMapper); + } + + @Override + public FloatingRateData retrieveOne(final Long floatingRateId) { + try { + FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(true); + final String sql = "select " + rateMapper.schema() + + " where rate.id = ?"; + return this.jdbcTemplate.queryForObject(sql, rateMapper, + new Object[] { floatingRateId }); + } catch (final EmptyResultDataAccessException e) { + throw new FloatingRateNotFoundException(floatingRateId); + } + } + + @Override + public List retrieveInterestRatePeriods( + final Long floatingRateId) { + try { + FloatingInterestRatePeriodRowMapper mapper = new FloatingInterestRatePeriodRowMapper(); + return this.jdbcTemplate.query(mapper.schema(), mapper, + new Object[] { floatingRateId }); + } catch (final EmptyResultDataAccessException e) { + throw new FloatingRateNotFoundException(floatingRateId); + } + } + + @Override + public FloatingRateData retrieveBaseLendingRate() { + try { + FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(true); + final String sql = "select " + + rateMapper.schema() + + " where rate.is_base_lending_rate = 1 and rate.is_active = 1"; + return this.jdbcTemplate.queryForObject(sql, rateMapper); + } catch (final EmptyResultDataAccessException e) { + throw new FloatingRateNotFoundException( + "error.msg.floatingrate.base.lending.rate.not.found"); + } + } + + private final class FloatingRateRowMapper implements + RowMapper { + + private final boolean addRatePeriods; + + private final StringBuilder sqlQuery = new StringBuilder() + .append("rate.id as id, ") + .append("rate.name as name, ") + .append("rate.is_base_lending_rate as isBaseLendingRate, ") + .append("rate.is_active as isActive, ") + .append("crappu.username as createdBy, ") + .append("rate.created_date as createdOn, ") + .append("moappu.username as modifiedBy, ") + .append("rate.lastmodified_date as modifiedOn ") + .append("FROM m_floating_rates as rate ") + .append("LEFT JOIN m_appuser as crappu on rate.createdby_id = crappu.id ") + .append("LEFT JOIN m_appuser as moappu on rate.lastmodifiedby_id = moappu.id "); + + public FloatingRateRowMapper(final boolean addRatePeriods) { + this.addRatePeriods = addRatePeriods; + } + + @Override + public FloatingRateData mapRow(final ResultSet rs, + @SuppressWarnings("unused") final int rowNum) + throws SQLException { + final Long id = rs.getLong("id"); + final String name = rs.getString("name"); + final boolean isBaseLendingRate = rs + .getBoolean("isBaseLendingRate"); + final boolean isActive = rs.getBoolean("isActive"); + final String createdBy = rs.getString("createdBy"); + final Date createdOn = rs.getDate("createdOn"); + final String modifiedBy = rs.getString("modifiedBy"); + final Date modifiedOn = rs.getDate("modifiedOn"); + List ratePeriods = null; + if (addRatePeriods) { + FloatingRatePeriodRowMapper ratePeriodMapper = new FloatingRatePeriodRowMapper(); + final String sql = "select " + + ratePeriodMapper.schema() + + " where period.is_active = 1 and period.floating_rates_id = ? " + + " order by period.from_date desc "; + ratePeriods = jdbcTemplate.query(sql, ratePeriodMapper, + new Object[] { id }); + } + return new FloatingRateData(id, name, isBaseLendingRate, isActive, + createdBy, createdOn, modifiedBy, modifiedOn, ratePeriods, + null); + } + + public String schema() { + return sqlQuery.toString(); + } + } + + private final class FloatingRatePeriodRowMapper implements + RowMapper { + + private final StringBuilder sqlQuery = new StringBuilder() + .append("period.id as id, ") + .append("period.from_date as fromDate, ") + .append("period.interest_rate as interestRate, ") + .append("period.is_differential_to_base_lending_rate as isDifferentialToBaseLendingRate, ") + .append("period.is_active as isActive, ") + .append("crappu.username as createdBy, ") + .append("period.created_date as createdOn, ") + .append("moappu.username as modifiedBy, ") + .append("period.lastmodified_date as modifiedOn ") + .append("FROM m_floating_rates_periods as period ") + .append("LEFT JOIN m_appuser as crappu on period.createdby_id = crappu.id ") + .append("LEFT JOIN m_appuser as moappu on period.lastmodifiedby_id = moappu.id "); + + @Override + public FloatingRatePeriodData mapRow(final ResultSet rs, + @SuppressWarnings("unused") final int rowNum) + throws SQLException { + final Long id = rs.getLong("id"); + final Date fromDate = rs.getDate("fromDate"); + final BigDecimal interestRate = rs.getBigDecimal("interestRate"); + final boolean isDifferentialToBaseLendingRate = rs + .getBoolean("isDifferentialToBaseLendingRate"); + final boolean isActive = rs.getBoolean("isActive"); + final String createdBy = rs.getString("createdBy"); + final Date createdOn = rs.getDate("createdOn"); + final String modifiedBy = rs.getString("modifiedBy"); + final Date modifiedOn = rs.getDate("modifiedOn"); + return new FloatingRatePeriodData(id, fromDate, interestRate, + isDifferentialToBaseLendingRate, isActive, createdBy, + createdOn, modifiedBy, modifiedOn); + } + + public String schema() { + return sqlQuery.toString(); + } + } + + private final class FloatingRateLookupMapper implements + RowMapper { + + private final StringBuilder sqlQuery = new StringBuilder() + .append("rate.id as id, ").append("rate.name as name, ") + .append("rate.is_base_lending_rate as isBaseLendingRate ") + .append("FROM m_floating_rates as rate "); + + @Override + public FloatingRateData mapRow(final ResultSet rs, + @SuppressWarnings("unused") final int rowNum) + throws SQLException { + final Long id = rs.getLong("id"); + final String name = rs.getString("name"); + final boolean isBaseLendingRate = rs + .getBoolean("isBaseLendingRate"); + return new FloatingRateData(id, name, isBaseLendingRate, true, + null, null, null, null, null, null); + } + + public String schema() { + return sqlQuery.toString(); + } + } + + private final class FloatingInterestRatePeriodRowMapper implements + RowMapper { + + private final StringBuilder sqlQuery = new StringBuilder() + .append("select ") + .append(" linkedrateperiods.from_date as linkedrateperiods_from_date, ") + .append(" linkedrateperiods.interest_rate as linkedrateperiods_interest_rate, ") + .append(" linkedrateperiods.is_differential_to_base_lending_rate as linkedrateperiods_is_differential_to_base_lending_rate, ") + .append(" baserate.from_date as baserate_from_date, ") + .append(" baserate.interest_rate as baserate_interest_rate ") + .append("from m_floating_rates as linkedrate ") + .append("left join m_floating_rates_periods as linkedrateperiods on (linkedrate.id = linkedrateperiods.floating_rates_id and linkedrateperiods.is_active = 1) ") + .append("left join ( ") + .append(" select blr.name, ") + .append(" blr.is_base_lending_rate, ") + .append(" blr.is_active, ") + .append(" blrperiods.from_date, ") + .append(" blrperiods.interest_rate ") + .append(" from m_floating_rates as blr ") + .append(" left join m_floating_rates_periods as blrperiods on (blr.id = blrperiods.floating_rates_id and blrperiods.is_active = 1) ") + .append(" where blr.is_base_lending_rate = 1 and blr.is_active = 1 ") + .append(") as baserate on (linkedrateperiods.is_differential_to_base_lending_rate = 1 and linkedrate.is_base_lending_rate = 0) ") + .append("where (baserate.from_date is null ") + .append(" or baserate.from_date = (select MAX(b.from_date) ") + .append(" from (select blr.name, ") + .append(" blr.is_base_lending_rate, ") + .append(" blr.is_active, ") + .append(" blrperiods.from_date, ") + .append(" blrperiods.interest_rate ") + .append(" from m_floating_rates as blr ") + .append(" left join m_floating_rates_periods as blrperiods on (blr.id = blrperiods.floating_rates_id and blrperiods.is_active = 1) ") + .append(" where blr.is_base_lending_rate = 1 and blr.is_active = 1 ") + .append(" ) as b ") + .append(" where b.from_date <= linkedrateperiods.from_date)) ") + .append("and linkedrate.id = ? ") + .append("order by linkedratePeriods_from_date desc "); + + @Override + public InterestRatePeriodData mapRow(final ResultSet rs, + @SuppressWarnings("unused") final int rowNum) + throws SQLException { + final Date fromDate = rs.getDate("linkedrateperiods_from_date"); + final BigDecimal interestRate = rs + .getBigDecimal("linkedrateperiods_interest_rate"); + final boolean isDifferentialToBLR = rs + .getBoolean("linkedrateperiods_is_differential_to_base_lending_rate"); + final Date blrFromDate = rs.getDate("baserate_from_date"); + final BigDecimal blrInterestRate = rs + .getBigDecimal("baserate_interest_rate"); + + return new InterestRatePeriodData(fromDate, interestRate, + isDifferentialToBLR, blrFromDate, blrInterestRate); + } + + public String schema() { + return sqlQuery.toString(); + } + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java index 32e88f566..b9edea8a0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java @@ -34,6 +34,10 @@ public interface LoanApiConstants { public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod"; public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod"; + // Floating interest rate related + public static final String interestRateDifferentialParameterName = "interestRateDifferential"; + public static final String isFloatingInterestRateParameterName = "isFloatingInterestRate"; + // Error codes public static final String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE= "loancharge.with.calculation.type.interest.not.allowed"; public static final String LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE= "loancharge.with.calculation.type.principal.not.allowed"; @@ -44,6 +48,8 @@ public interface LoanApiConstants { public static final String APPROVED_AMOUNT_IS_LESS_THAN_SUM_OF_TRANCHES = "sum.of.multi.disburse.amounts.must.be.equal.to.or.lesser.than.approved.principal"; public static final String DISBURSEMENT_DATES_NOT_IN_ORDER = "disbursements.should.be.ordered.based.on.their.disbursement.dates"; public static final String DISBURSEMENT_DATE_BEFORE_ERROR = "disbursement.date.of.tranche.cannot.be.before.expected.disbursement.date"; - + + public static final String isFloatingInterestRate = "isFloatingInterestRate"; + public static final String interestRateDifferential = "interestRateDifferential"; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index 93df1a58b..d168997e2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.loanaccount.api; +import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestType; + import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -59,6 +61,7 @@ import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.collateral.data.CollateralData; import org.mifosplatform.portfolio.collateral.service.CollateralReadPlatformService; +import org.mifosplatform.portfolio.floatingrates.data.InterestRatePeriodData; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; @@ -85,6 +88,7 @@ import org.mifosplatform.portfolio.loanaccount.service.LoanChargeReadPlatformSer import org.mifosplatform.portfolio.loanaccount.service.LoanReadPlatformService; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData; +import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.service.LoanDropdownReadPlatformService; import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.mifosplatform.portfolio.note.data.NoteData; @@ -105,7 +109,7 @@ import com.google.gson.JsonElement; public class LoansApiResource { private final Set LOAN_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "accountNo", "status", "externalId", "clientId", - "group", "loanProductId", "loanProductName", "loanProductDescription", "fundId", "fundName", "loanPurposeId", + "group", "loanProductId", "loanProductName", "loanProductDescription", "isLoanProductLinkedToFloatingRate", "fundId", "fundName", "loanPurposeId", "loanPurposeName", "loanOfficerId", "loanOfficerName", "currency", "principal", "totalOverpaid", "inArrearsTolerance", "termFrequency", "termPeriodFrequencyType", "numberOfRepayments", "repaymentEvery", "interestRatePerPeriod", "annualInterestRate", "repaymentFrequencyType", "transactionProcessingStrategyId", "transactionProcessingStrategyName", @@ -117,7 +121,7 @@ public class LoansApiResource { "repaymentFrequencyDaysOfWeekTypeOptions", "termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting", "loanCounter", "loanProductCounter", "notes", - "accountLinkingOptions", "linkedAccount")); + "accountLinkingOptions", "linkedAccount", "interestRateDifferential", "isFloatingInterestRate", "interestRatesPeriods")); private final Set LOAN_APPROVAL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("approvalDate", "approvalAmount")); private final String resourceNameForPermissions = "LOAN"; @@ -354,6 +358,8 @@ public class LoansApiResource { compoundingCalendarData); } + Collection interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanId); + Collection loanRepayments = null; LoanScheduleData repaymentSchedule = null; Collection charges = null; @@ -488,7 +494,11 @@ public class LoansApiResource { interestRateFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); - interestTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestTypeOptions(); + if(product.isLinkedToFloatingInterestRates()){ + interestTypeOptions = Arrays.asList(interestType(InterestMethod.DECLINING_BALANCE)); + }else { + interestTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestTypeOptions(); + } interestCalculationPeriodTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestRateCalculatedInPeriodOptions(); fundOptions = this.fundReadPlatformService.retrieveAllFunds(); @@ -537,7 +547,7 @@ public class LoansApiResource { null, null, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations, - overdueCharges, paidInAdvanceTemplate); + overdueCharges, paidInAdvanceTemplate, interestRatesPeriods); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), mandatoryResponseParameters); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java index e390511cf..cba49d793 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java @@ -27,6 +27,7 @@ import org.mifosplatform.portfolio.account.data.PortfolioAccountData; import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.collateral.data.CollateralData; +import org.mifosplatform.portfolio.floatingrates.data.InterestRatePeriodData; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; @@ -64,6 +65,7 @@ public class LoanAccountData { private final Long loanProductId; private final String loanProductName; private final String loanProductDescription; + private final boolean isLoanProductLinkedToFloatingRate; private final Long fundId; private final String fundName; private final Long loanPurposeId; @@ -88,6 +90,8 @@ public class LoanAccountData { private final BigDecimal interestRatePerPeriod; private final EnumOptionData interestRateFrequencyType; private final BigDecimal annualInterestRate; + private final boolean isFloatingInterestRate; + private final BigDecimal interestRateDifferential; // settings private final EnumOptionData amortizationType; @@ -182,6 +186,8 @@ public class LoanAccountData { // Paid In Advance private final PaidInAdvanceData paidInAdvance; + + private final Collection interestRatesPeriods; /** * Used to produce a {@link LoanAccountData} with only collateral options @@ -200,6 +206,7 @@ public class LoanAccountData { final Long loanProductId = null; final String loanProductName = null; final String loanProductDescription = null; + final boolean isLoanProductLinkedToFloatingRate = false; final Long fundId = null; final String fundName = null; final Long loanPurposeId = null; @@ -225,6 +232,8 @@ public class LoanAccountData { final EnumOptionData interestRateFrequencyType = null; final BigDecimal annualInterestRate = null; final EnumOptionData interestType = null; + final boolean isFloatingInterestRate = false; + final BigDecimal interestRateDifferential = null; final EnumOptionData interestCalculationPeriodType = null; final LocalDate expectedFirstRepaymentOnDate = null; final Integer graceOnPrincipalPayment = null; @@ -283,13 +292,15 @@ public class LoanAccountData { final LoanScheduleData originalSchedule = null; final Boolean createStandingInstructionAtDisbursement = null; final PaidInAdvanceData paidInAdvance = null; + final Collection interestRatesPeriods = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, - loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, + loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, - interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, + interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, + interestRateDifferential, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, @@ -300,7 +311,7 @@ public class LoanAccountData { disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, - createStandingInstructionAtDisbursement, paidInAdvance); + createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods); } @@ -320,6 +331,7 @@ public class LoanAccountData { final Long loanProductId = null; final String loanProductName = null; final String loanProductDescription = null; + final boolean isLoanProductLinkedToFloatingRate = false; final Long fundId = null; final String fundName = null; final Long loanPurposeId = null; @@ -345,6 +357,8 @@ public class LoanAccountData { final EnumOptionData interestRateFrequencyType = null; final BigDecimal annualInterestRate = null; final EnumOptionData interestType = null; + final boolean isFloatingInterestRate = false; + final BigDecimal interestRateDifferential = null; final EnumOptionData interestCalculationPeriodType = null; final LocalDate expectedFirstRepaymentOnDate = null; final Integer graceOnPrincipalPayment = null; @@ -404,13 +418,15 @@ public class LoanAccountData { final LoanScheduleData originalSchedule = null; final Boolean createStandingInstructionAtDisbursement = null; final PaidInAdvanceData paidInAdvance = null; + final Collection interestRatesPeriods = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, - loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, + loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, - interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, + interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, + isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, @@ -421,20 +437,21 @@ public class LoanAccountData { disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, - createStandingInstructionAtDisbursement, paidInAdvance); + createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods); } public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, clientAcc.clientId, clientAcc.clientName, - clientAcc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, + clientAcc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, - acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, clientAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, @@ -447,7 +464,7 @@ public class LoanAccountData { acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, - acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); + acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods); } /** @@ -467,6 +484,7 @@ public class LoanAccountData { final Long loanProductId = null; final String loanProductName = null; final String loanProductDescription = null; + final boolean isLoanProductLinkedToFloatingRate = false; final Long fundId = null; final String fundName = null; final Long loanPurposeId = null; @@ -492,6 +510,8 @@ public class LoanAccountData { final EnumOptionData interestRateFrequencyType = null; final BigDecimal annualInterestRate = null; final EnumOptionData interestType = null; + final boolean isFloatingInterestRate = false; + final BigDecimal interestRateDifferential = null; final EnumOptionData interestCalculationPeriodType = null; final LocalDate expectedFirstRepaymentOnDate = null; final Integer graceOnPrincipalPayment = null; @@ -551,13 +571,15 @@ public class LoanAccountData { final LoanScheduleData originalSchedule = null; final Boolean createStandingInstructionAtDisbursement = null; final PaidInAdvanceData paidInAdvance = null; + final Collection interestRatesPeriods = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, - loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, + loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, - interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, + interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, + isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, @@ -568,20 +590,21 @@ public class LoanAccountData { disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, - createStandingInstructionAtDisbursement, paidInAdvance); + createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods); } public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, - groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, + groupAcc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, - acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, groupAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, @@ -594,7 +617,7 @@ public class LoanAccountData { acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, - acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); + acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods); } @@ -712,14 +735,16 @@ public class LoanAccountData { final LoanScheduleData originalSchedule = null; final Boolean createStandingInstructionAtDisbursement = null; final PaidInAdvanceData paidInAdvance = null; + final Collection interestRatesPeriods = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, - product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), loanPurposeId, + product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(), proposedPrincipal, principal, principal, totalOverpaid, product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, numberOfRepayments, product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(), transactionProcessingStrategyName, product.getAmortizationType(), interestRatePerPeriod, product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(), + product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(), product.getInterestCalculationPeriodType(), expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, @@ -732,7 +757,7 @@ public class LoanAccountData { product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(), - originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance); + originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods); } public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) { @@ -769,13 +794,14 @@ public class LoanAccountData { } return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, - acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), product.getFundId(), + acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, product.getCurrency(), product.getPrincipal(), product.getPrincipal(), product.getPrincipal(), acc.totalOverpaid, product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(), product.getTransactionProcessingStrategyName(), product.getAmortizationType(), product.getInterestRatePerPeriod(), product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(), + product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(), product.getInterestCalculationPeriodType(), acc.expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, @@ -787,7 +813,8 @@ public class LoanAccountData { product.canDefineInstallmentAmount(), acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(), - product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance); + product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement, + paidInAdvance, acc.interestRatesPeriods); } @@ -798,6 +825,7 @@ public class LoanAccountData { public static LoanAccountData basicLoanDetails(final Long id, final String accountNo, final LoanStatusEnumData status, final String externalId, final Long clientId, final String clientName, final Long clientOfficeId, final GroupGeneralData group, final EnumOptionData loanType, final Long loanProductId, final String loanProductName, final String loanProductDescription, + final boolean isLoanProductLinkedToFloatingRate, final Long fundId, final String fundName, final Long loanPurposeId, final String loanPurposeName, final Long loanOfficerId, final String loanOfficerName, final CurrencyData currencyData, final BigDecimal proposedPrincipal, final BigDecimal principal, final BigDecimal approvedPrincipal, final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance, @@ -805,7 +833,8 @@ public class LoanAccountData { final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, EnumOptionData repaymentFrequencyNthDayType, EnumOptionData repaymentFrequencyDayOfWeekType, final Long transactionStrategyId, final String transactionStrategyName, final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType, - final BigDecimal annualInterestRate, final EnumOptionData interestType, final EnumOptionData interestCalculationPeriodType, + final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate, + final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType, final LocalDate expectedFirstRepaymentOnDate, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged, @@ -848,13 +877,15 @@ public class LoanAccountData { final Collection overdueCharges = null; final LoanScheduleData originalSchedule = null; final PaidInAdvanceData paidInAdvance = null; + final Collection interestRatesPeriods = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, - loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, + loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod, - interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, + interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, + interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, @@ -865,7 +896,7 @@ public class LoanAccountData { disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmont, outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, - createStandingInstructionAtDisbursement, paidInAdvance); + createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods); } /* @@ -888,20 +919,21 @@ public class LoanAccountData { final Collection notes, final Collection accountLinkingOptions, final PortfolioAccountData linkedAccount, final Collection disbursementDetails, final Collection emiAmountVariations, final Collection overdueCharges, - final PaidInAdvanceData paidInAdvance) { + final PaidInAdvanceData paidInAdvance, Collection interestRatesPeriods) { LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; if (acc.product != null) { loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes(); } return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, - acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, + acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, - acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, @@ -913,7 +945,7 @@ public class LoanAccountData { acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, - acc.createStandingInstructionAtDisbursement, paidInAdvance); + acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods); } public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection productOptions, @@ -925,18 +957,19 @@ public class LoanAccountData { acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions, - acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance); + acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance, acc.interestRatesPeriods); } public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, - group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, + group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, - acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, @@ -949,7 +982,7 @@ public class LoanAccountData { acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, - acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); + acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods); } public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map memberLoanCycle) { @@ -991,13 +1024,14 @@ public class LoanAccountData { } return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, - acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, + acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, - acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, @@ -1010,7 +1044,7 @@ public class LoanAccountData { acc.maxOutstandingLoanBalance, acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, - acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); + acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods); } @@ -1020,13 +1054,14 @@ public class LoanAccountData { final LoanInterestRecalculationData interestRecalculationData = LoanInterestRecalculationData.withCalendarData( acc.interestRecalculationData, calendarData, compoundingCalendarData); return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, - acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, + acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, - acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, @@ -1039,19 +1074,20 @@ public class LoanAccountData { acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule, - acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); + acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods); } public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, - acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, + acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, - acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, + acc.annualInterestRate, acc.interestType, acc.isFloatingInterestRate, acc.interestRateDifferential, + acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, @@ -1064,7 +1100,7 @@ public class LoanAccountData { acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule, - acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); + acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods); } private LoanAccountData( @@ -1080,6 +1116,7 @@ public class LoanAccountData { final Long loanProductId, final String loanProductName, final String loanProductDescription, // + final boolean isLoanProductLinkedToFloatingRate, final Long fundId, final String fundName, final Long loanPurposeId, @@ -1100,7 +1137,8 @@ public class LoanAccountData { final EnumOptionData repaymentFrequencyNthDayType, final EnumOptionData repaymentFrequencyDayOfWeekType, final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName, final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType, - final BigDecimal annualInterestRate, final EnumOptionData interestType, final EnumOptionData interestCalculationPeriodType, + final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate, + final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType, final LocalDate expectedFirstRepaymentOnDate, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData summary, final BigDecimal feeChargesDueAtDisbursementCharged, final LoanScheduleData repaymentSchedule, @@ -1125,7 +1163,8 @@ public class LoanAccountData { final Integer graceOnArrearsAgeing, final Collection overdueCharges, final Boolean isNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled, final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule, - final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance) { + final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance, + final Collection interestRatesPeriods) { this.id = id; this.accountNo = accountNo; @@ -1139,6 +1178,7 @@ public class LoanAccountData { this.loanProductId = loanProductId; this.loanProductName = loanProductName; this.loanProductDescription = loanProductDescription; + this.isLoanProductLinkedToFloatingRate = isLoanProductLinkedToFloatingRate; this.fundId = fundId; this.fundName = fundName; this.loanPurposeId = loanPurposeId; @@ -1165,6 +1205,8 @@ public class LoanAccountData { this.interestRateFrequencyType = interestRateFrequencyType; this.annualInterestRate = annualInterestRate; this.interestType = interestType; + this.isFloatingInterestRate = isFloatingInterestRate; + this.interestRateDifferential = interestRateDifferential; this.interestCalculationPeriodType = interestCalculationPeriodType; this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate; this.graceOnPrincipalPayment = graceOnPrincipalPayment; @@ -1289,6 +1331,7 @@ public class LoanAccountData { if (this.product != null) { this.product.setloanProductConfigurableAttributes(product.getloanProductConfigurableAttributes()); } + this.interestRatesPeriods = interestRatesPeriods; } public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() { @@ -1395,4 +1438,5 @@ public class LoanAccountData { if(this.summary != null) return this.summary.getTotalPaidFeeCharges() ; return BigDecimal.ZERO ; } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTermVariationsData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTermVariationsData.java index 4b9041729..b06a554c3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTermVariationsData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanTermVariationsData.java @@ -9,6 +9,7 @@ import java.math.BigDecimal; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType; public class LoanTermVariationsData { @@ -16,6 +17,7 @@ public class LoanTermVariationsData { private final Long id; private final EnumOptionData termType; private final LocalDate termVariationApplicableFrom; + private final Integer termVariationApplicableFromInstallment; private final BigDecimal termValue; public LoanTermVariationsData(final Long id, final EnumOptionData termType, final LocalDate termVariationApplicableFrom, @@ -24,11 +26,25 @@ public class LoanTermVariationsData { this.termType = termType; this.termVariationApplicableFrom = termVariationApplicableFrom; this.termValue = termValue; + this.termVariationApplicableFromInstallment = null; + } + + public LoanTermVariationsData(final EnumOptionData termType, final LocalDate termVariationApplicableFrom, + final BigDecimal termValue) { + this.id = null; + this.termType = termType; + this.termVariationApplicableFrom = termVariationApplicableFrom; + this.termValue = termValue; + this.termVariationApplicableFromInstallment = null; } public EnumOptionData getTermType() { return this.termType; } + + public LoanTermVariationType getTermVariationType(){ + return LoanTermVariationType.fromInt(this.termType.getId().intValue()); + } public LocalDate getTermApplicableFrom() { return this.termVariationApplicableFrom; @@ -37,4 +53,30 @@ public class LoanTermVariationsData { public BigDecimal getTermValue() { return this.termValue; } + + public boolean isApplicable(final LocalDate fromDate, final LocalDate dueDate, int installmentNumber) { + return occursOnDayFromAndUpTo(fromDate, dueDate, this.termVariationApplicableFrom) + || isIntegerEquals(installmentNumber, this.termVariationApplicableFromInstallment); + } + + private boolean occursOnDayFromAndUpTo(final LocalDate fromInclusive, final LocalDate upToNotInclusive, final LocalDate target) { + return target != null && !target.isBefore(fromInclusive) && target.isBefore(upToNotInclusive); + } + + private boolean isIntegerEquals(final Integer installmentNumber, final Integer target) { + return target != null && target.equals(installmentNumber); + } + + public boolean isApplicable(final LocalDate fromDate, int installmentNumber) { + return occursBefore(fromDate, this.termVariationApplicableFrom) + || isIntegerGreterThan(installmentNumber, this.termVariationApplicableFromInstallment); + } + + private boolean occursBefore(final LocalDate date, final LocalDate target) { + return target != null && target.isBefore(date); + } + + private boolean isIntegerGreterThan(final Integer installmentNumber, final Integer target) { + return target != null && target < installmentNumber; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java index a9ca10365..046452e5c 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/ScheduleGeneratorDTO.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.data; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; public class ScheduleGeneratorDTO { @@ -20,10 +21,12 @@ public class ScheduleGeneratorDTO { final CalendarInstance compoundingCalendarInstance; LocalDate recalculateFrom; final Long overdurPenaltyWaitPeriod; + final FloatingRateDTO floatingRateDTO; public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO, - final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance) { + final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance, + final FloatingRateDTO floatingRateDTO) { this.loanScheduleFactory = loanScheduleFactory; this.applicationCurrency = applicationCurrency; @@ -33,13 +36,14 @@ public class ScheduleGeneratorDTO { this.recalculateFrom = null; this.overdurPenaltyWaitPeriod = null; this.holidayDetailDTO = holidayDetailDTO; + this.floatingRateDTO = floatingRateDTO; } public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO, final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance, - final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod) { + final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod, final FloatingRateDTO floatingRateDTO) { this.loanScheduleFactory = loanScheduleFactory; this.applicationCurrency = applicationCurrency; @@ -49,7 +53,7 @@ public class ScheduleGeneratorDTO { this.recalculateFrom = recalculateFrom; this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod; this.holidayDetailDTO = holidayDetailDTO; - + this.floatingRateDTO = floatingRateDTO; } public LoanScheduleGeneratorFactory getLoanScheduleFactory() { @@ -96,4 +100,9 @@ public class ScheduleGeneratorDTO { return this.compoundingCalendarInstance; } + + public FloatingRateDTO getFloatingRateDTO() { + return this.floatingRateDTO; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index f1cfb3f38..e0e60332d 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -77,6 +77,8 @@ import org.mifosplatform.portfolio.collateral.domain.LoanCollateral; import org.mifosplatform.portfolio.common.domain.DayOfWeekType; import org.mifosplatform.portfolio.common.domain.NthDayType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; @@ -369,18 +371,26 @@ public class Loan extends AbstractPersistable { @Column(name = "interest_recalcualated_on") private Date interestRecalculatedOn; + @Column(name = "is_floating_interest_rate", nullable = true) + private Boolean isFloatingInterestRate; + + @Column(name = "interest_rate_differential", scale = 6, precision = 19, nullable = true) + private BigDecimal interestRateDifferential; + public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges, final Set collateral, final BigDecimal fixedEmiAmount, final Set disbursementDetails, - final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement) { + final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, + final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) { final LoanStatus status = null; final Group group = null; final Boolean syncDisbursementWithMeeting = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, - disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement); + disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, + interestRateDifferential); } public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType, @@ -389,12 +399,14 @@ public class Loan extends AbstractPersistable { final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges, final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final Set disbursementDetails, final BigDecimal maxOutstandingLoanBalance, - final Boolean createStandingInstructionAtDisbursement) { + final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, + final BigDecimal interestRateDifferential) { final LoanStatus status = null; final Client client = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, - disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement); + disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, + interestRateDifferential); } public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group, @@ -403,11 +415,13 @@ public class Loan extends AbstractPersistable { final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges, final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final Set disbursementDetails, final BigDecimal maxOutstandingLoanBalance, - final Boolean createStandingInstructionAtDisbursement) { + final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, + final BigDecimal interestRateDifferential) { final LoanStatus status = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, - disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement); + disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, + interestRateDifferential); } protected Loan() { @@ -419,11 +433,15 @@ public class Loan extends AbstractPersistable { final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus, final Set loanCharges, final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final Set disbursementDetails, - final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement) { + final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, + final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) { this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail; this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings(); + this.isFloatingInterestRate = isFloatingInterestRate; + this.interestRateDifferential = interestRateDifferential; + if (StringUtils.isBlank(accountNo)) { this.accountNumber = new RandomPasswordGenerator(19).generate(); this.accountNumberRequiresAutoGeneration = true; @@ -1288,6 +1306,20 @@ public class Loan extends AbstractPersistable { actualChanges.put("recalculateLoanSchedule", true); } + final String isFloatingInterestRateParamName = "isFloatingInterestRate"; + if (command.isChangeInBooleanParameterNamed(isFloatingInterestRateParamName, this.isFloatingInterestRate)) { + final Boolean newValue = command.booleanObjectValueOfParameterNamed(isFloatingInterestRateParamName); + actualChanges.put(isFloatingInterestRateParamName, newValue); + this.isFloatingInterestRate = newValue; + } + + final String interestRateDifferentialParamName = "interestRateDifferential"; + if (command.isChangeInBigDecimalParameterNamed(interestRateDifferentialParamName, this.interestRateDifferential)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRateDifferentialParamName); + actualChanges.put(interestRateDifferentialParamName, newValue); + this.interestRateDifferential = newValue; + } + Long existingFundId = null; if (this.fund != null) { existingFundId = this.fund.getId(); @@ -1498,6 +1530,7 @@ public class Loan extends AbstractPersistable { for (final LoanCharge loanCharge : charges) { recalculateLoanCharge(loanCharge, penaltyWaitPeriod); } + updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); } public boolean isInterestRecalculationEnabledForProduct() { @@ -2578,6 +2611,23 @@ public class Loan extends AbstractPersistable { rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); } + BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); + Collection loanTermVariations = new ArrayList<>(); + if (loanProduct.isLinkedToFloatingInterestRate()) { + Collection applicableRates = loanProduct.fetchInterestRates(scheduleGeneratorDTO.getFloatingRateDTO()); + LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant(); + for (FloatingRatePeriodData periodData : applicableRates) { + LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( + LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), + periodData.getInterestRate()); + if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) { + interestRateStartDate = periodData.getFromDateAsLocalDate(); + annualNominalInterestRate = periodData.getInterestRate(); + } + loanTermVariations.add(loanTermVariation); + } + } + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(), loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), @@ -2585,7 +2635,8 @@ public class Loan extends AbstractPersistable { this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, - this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, getApprovedPrincipal()); + this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, getApprovedPrincipal(), + annualNominalInterestRate, loanTermVariations); final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(), scheduleGeneratorDTO.getHolidayDetailDTO()); @@ -4839,6 +4890,8 @@ public class Loan extends AbstractPersistable { */ if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser); + } else { + regenerateRepaymentSchedule(generatorDTO, currentUser); } return processTransactions(); @@ -4929,7 +4982,7 @@ public class Loan extends AbstractPersistable { final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO.getApplicationCurrency(), generatorDTO.getCalculatedRepaymentsStartingFromDate(), generatorDTO.getCalendarInstanceForInterestRecalculation(), - generatorDTO.getCompoundingCalendarInstance()); + generatorDTO.getCompoundingCalendarInstance(), generatorDTO.getFloatingRateDTO()); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); @@ -4941,7 +4994,7 @@ public class Loan extends AbstractPersistable { public LoanApplicationTerms constructLoanApplicationTerms(final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation, - final CalendarInstance compoundingCalendarInstance) { + final CalendarInstance compoundingCalendarInstance, final FloatingRateDTO floatingRateDTO) { final Integer loanTermFrequency = this.termFrequency; final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); final List disbursementData = new ArrayList<>(); @@ -4967,6 +5020,23 @@ public class Loan extends AbstractPersistable { RecalculationFrequencyType recalculationFrequencyType = this.loanInterestRecalculationDetails().getRestFrequencyType(); RecalculationFrequencyType compoundingFrequencyType = this.loanInterestRecalculationDetails().getCompoundingFrequencyType(); + BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); + Collection loanTermVariations = new ArrayList<>(); + if (loanProduct.isLinkedToFloatingInterestRate()) { + Collection applicableRates = loanProduct.fetchInterestRates(floatingRateDTO); + LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant(); + for (FloatingRatePeriodData periodData : applicableRates) { + LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( + LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), + periodData.getInterestRate()); + if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) { + interestRateStartDate = periodData.getFromDateAsLocalDate(); + annualNominalInterestRate = periodData.getInterestRate(); + } + loanTermVariations.add(loanTermVariation); + } + } + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, @@ -4975,13 +5045,13 @@ public class Loan extends AbstractPersistable { calendarInstanceForInterestRecalculation, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy(), - getApprovedPrincipal()); + getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations); return loanApplicationTerms; } public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, - final HolidayDetailDTO holidayDetailDTO, final LocalDate onDate) { + final HolidayDetailDTO holidayDetailDTO, final LocalDate onDate, FloatingRateDTO floatingRateDTO) { LoanRepaymentScheduleInstallment installment = null; if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { @@ -5028,6 +5098,23 @@ public class Loan extends AbstractPersistable { rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); } + BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); + Collection loanTermVariations = new ArrayList<>(); + if (loanProduct.isLinkedToFloatingInterestRate()) { + Collection applicableRates = loanProduct.fetchInterestRates(floatingRateDTO); + LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant(); + for (FloatingRatePeriodData periodData : applicableRates) { + LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( + LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), + periodData.getInterestRate()); + if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) { + interestRateStartDate = periodData.getFromDateAsLocalDate(); + annualNominalInterestRate = periodData.getInterestRate(); + } + loanTermVariations.add(loanTermVariation); + } + } + final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(null, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, @@ -5035,7 +5122,7 @@ public class Loan extends AbstractPersistable { this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, - getApprovedPrincipal()); + getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(), @@ -5076,7 +5163,7 @@ public class Loan extends AbstractPersistable { public LocalDate fetchInterestRecalculateFromDate() { LocalDate interestRecalculatedOn = null; - if (this.interestRecalculatedOn != null) { + if (this.interestRecalculatedOn == null) { interestRecalculatedOn = getDisbursementDate(); } else { interestRecalculatedOn = new LocalDate(this.interestRecalculatedOn); @@ -5189,13 +5276,16 @@ public class Loan extends AbstractPersistable { * TODO * @param compoundingCalendarInstance * TODO + * @param floatingRateDTO + * TODO * @param loanCalendarInstance * Used for accessing the loan's calendar object * @return application terms of the Loan object **/ @SuppressWarnings({ "unused" }) public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency, - final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar) { + final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, + final FloatingRateDTO floatingRateDTO) { LoanProduct loanProduct = loanProduct(); // LoanProductRelatedDetail loanProductRelatedDetail = // getLoanRepaymentScheduleDetail(); @@ -5219,7 +5309,7 @@ public class Loan extends AbstractPersistable { final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod(); final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(); - final BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); + BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); final Money principalMoney = this.loanRepaymentScheduleDetail.getPrincipal(); final LocalDate expectedDisbursementDate = getExpectedDisbursedOnLocalDate(); @@ -5264,13 +5354,30 @@ public class Loan extends AbstractPersistable { rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); } + Collection loanTermVariations = new ArrayList<>(); + if (loanProduct.isLinkedToFloatingInterestRate()) { + Collection applicableRates = loanProduct.fetchInterestRates(floatingRateDTO); + LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant(); + for (FloatingRatePeriodData periodData : applicableRates) { + LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( + LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), + periodData.getInterestRate()); + if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) { + interestRateStartDate = periodData.getFromDateAsLocalDate(); + annualNominalInterestRate = periodData.getInterestRate(); + } + loanTermVariations.add(loanTermVariation); + } + } + return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, - this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal()); + this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), + annualNominalInterestRate, loanTermVariations); } /** @@ -5509,4 +5616,20 @@ public class Loan extends AbstractPersistable { } } + public Boolean getIsFloatingInterestRate() { + return this.isFloatingInterestRate; + } + + public BigDecimal getInterestRateDifferential() { + return this.interestRateDifferential; + } + + public void setIsFloatingInterestRate(Boolean isFloatingInterestRate) { + this.isFloatingInterestRate = isFloatingInterestRate; + } + + public void setInterestRateDifferential(BigDecimal interestRateDifferential) { + this.interestRateDifferential = interestRateDifferential; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 66d9b460d..def77af74 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -49,6 +49,9 @@ import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUS import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; @@ -88,6 +91,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { private final LoanAccrualPlatformService loanAccrualPlatformService; private final PlatformSecurityContext context; private final BusinessEventNotifierService businessEventNotifierService; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; @Autowired public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, @@ -101,7 +105,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { final CalendarInstanceRepository calendarInstanceRepository, final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository, final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context, - final BusinessEventNotifierService businessEventNotifierService) { + final BusinessEventNotifierService businessEventNotifierService, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { this.loanAccountAssembler = loanAccountAssembler; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -119,6 +124,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { this.loanAccrualPlatformService = loanAccrualPlatformService; this.context = context; this.businessEventNotifierService = businessEventNotifierService; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; } @Transactional @@ -190,9 +196,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment, @@ -239,6 +247,19 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { return newRepaymentTransaction; } + private FloatingRateDTO constructFloatingRateDTO(final Loan loan) { + FloatingRateDTO floatingRateDTO = null; + if (loan.loanProduct().isLinkedToFloatingInterestRate()) { + boolean isFloatingInterestRate = loan.getIsFloatingInterestRate(); + BigDecimal interestRateDiff = loan.getInterestRateDifferential(); + List baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() + .getRatePeriods(); + floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff, + baseLendingRatePeriods); + } + return floatingRateDTO; + } + private void saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) { try { this.loanTransactionRepository.save(newRepaymentTransaction); @@ -557,8 +578,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, floatingRateDTO); return scheduleGeneratorDTO; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java index 0bb08755c..2e9b279e9 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanRepository.java @@ -54,6 +54,8 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat public static final String DOES_CLIENT_HAVE_NON_CLOSED_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.client.id = :clientId and loan.loanStatus in (100,200,300,303,304,700)"; + public static final String DOES_PRODUCT_HAVE_NON_CLOSED_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.loanProduct.id = :productId and loan.loanStatus in (100,200,300,303,304,700)"; + @Query(FIND_GROUP_LOANS_DISBURSED_AFTER) List getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId, @Param("loanType") Integer loanType); @@ -126,5 +128,8 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat @Query(DOES_CLIENT_HAVE_NON_CLOSED_LOANS) boolean doNonClosedLoanAccountsExistForClient(@Param("clientId") Long clientId); + + @Query(DOES_PRODUCT_HAVE_NON_CLOSED_LOANS) + boolean doNonClosedLoanAccountsExistForProduct(@Param("productId") Long productId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariationType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariationType.java index 0f13be809..e2a778d2a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariationType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanTermVariationType.java @@ -8,7 +8,8 @@ package org.mifosplatform.portfolio.loanaccount.domain; public enum LoanTermVariationType { INVALID(0, "loanTermType.invalid"), // - EMI_AMOUNT(1, "loanTermType.emiAmount"); // + EMI_AMOUNT(1, "loanTermType.emiAmount"), // + INTEREST_RATE(2, "loanTermType.interestRate"); private final Integer value; private final String code; @@ -33,6 +34,9 @@ public enum LoanTermVariationType { case 1: enumeration = LoanTermVariationType.EMI_AMOUNT; break; + case 2: + enumeration = LoanTermVariationType.INTEREST_RATE; + break; } return enumeration; } @@ -40,4 +44,8 @@ public enum LoanTermVariationType { public boolean isEMIAmountVariation() { return this.value.equals(LoanTermVariationType.EMI_AMOUNT.getValue()); } + + public boolean isInterestRateVariation() { + return this.value.equals(LoanTermVariationType.INTEREST_RATE.getValue()); + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleRecalculationDTO.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleRecalculationDTO.java index 7565d6656..2f9803308 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleRecalculationDTO.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/data/LoanScheduleRecalculationDTO.java @@ -32,6 +32,7 @@ public class LoanScheduleRecalculationDTO { private final Map latePaymentMap; private final TreeMap compoundingMap; private final Map disburseDetailMap; + private final Money principalToBeScheduled; private final Money outstandingBalance; private final Money outstandingBalanceAsPerRest; private final List installments; @@ -44,9 +45,9 @@ public class LoanScheduleRecalculationDTO { final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected, final Money reducePrincipal, final Map principalPortionMap, final Map latePaymentMap, - final TreeMap compoundingMap, final Map disburseDetailMap, final Money outstandingBalance, - final Money outstandingBalanceAsPerRest, final List installments, - final Collection recalculationDetails, + final TreeMap compoundingMap, final Map disburseDetailMap, Money principalToBeScheduled, + final Money outstandingBalance, final Money outstandingBalanceAsPerRest, + final List installments, final Collection recalculationDetails, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, final boolean partialUpdate) { this.periodNumber = periodNumber; @@ -63,6 +64,7 @@ public class LoanScheduleRecalculationDTO { this.latePaymentMap = latePaymentMap; this.compoundingMap = compoundingMap; this.disburseDetailMap = disburseDetailMap; + this.principalToBeScheduled = principalToBeScheduled; this.outstandingBalance = outstandingBalance; this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest; this.installments = installments; @@ -77,14 +79,15 @@ public class LoanScheduleRecalculationDTO { final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected, final Money reducePrincipal, final Map principalPortionMap, final Map latePaymentMap, final TreeMap compoundingMap, - final Map disburseDetailMap, final Money outstandingBalance, final Money outstandingBalanceAsPerRest, - final List installments, final Collection recalculationDetails, + final Map disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance, + final Money outstandingBalanceAsPerRest, final List installments, + final Collection recalculationDetails, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) { final boolean partialUpdate = true; return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap, - outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, + principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate); } @@ -105,6 +108,7 @@ public class LoanScheduleRecalculationDTO { final Map latePaymentMap = null; final TreeMap compoundingMap = null; final Map disburseDetailMap = null; + final Money principalToBeScheduled = null; final Money outstandingBalance = null; final Money outstandingBalanceAsPerRest = null; final List installments = null; @@ -112,7 +116,7 @@ public class LoanScheduleRecalculationDTO { return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap, - outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, + principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate); } @@ -200,4 +204,9 @@ public class LoanScheduleRecalculationDTO { return this.periodStartDate; } + + public Money getPrincipalToBeScheduled() { + return this.principalToBeScheduled; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index dfeecd667..52cd96b74 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -28,8 +28,10 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; +import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -181,6 +183,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener extendTermForDailyRepayments = true; } + // for applying variations + Collection loanTermVariations = new ArrayList<>(loanApplicationTerms.getLoanTermVariations()); + // this block is to start the schedule generation from specified date if (loanScheduleRecalculationDTO != null && loanScheduleRecalculationDTO.isPartialUpdate()) { periodNumber = loanScheduleRecalculationDTO.getPeriodNumber(); @@ -206,6 +211,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener outstandingBalanceAsPerRest = loanScheduleRecalculationDTO.getOutstandingBalanceAsPerRest(); installments.clear(); installments.addAll(loanScheduleRecalculationDTO.getInstallments()); + if (loanApplicationTerms.isMultiDisburseLoan()) { + principalToBeScheduled = loanScheduleRecalculationDTO.getPrincipalToBeScheduled(); + loanApplicationTerms.setPrincipal(principalToBeScheduled); + } + for (LoanTermVariationsData variation : loanApplicationTerms.getLoanTermVariations()) { + if (variation.getTermVariationType().isInterestRateVariation() && variation.isApplicable(periodStartDate, periodNumber) + && variation.getTermValue() != null) { + loanApplicationTerms.updateAnnualNominalInterestRate(variation.getTermValue()); + } + } } while (!outstandingBalance.isZero() || !disburseDetailMap.isEmpty()) { @@ -241,6 +256,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Collection applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms, scheduledDueDate, transactions); + Collection applicableVariations = getApplicableTermVariationsForPeriod(periodStartDate, + scheduledDueDate, periodNumber, loanTermVariations); + double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate, loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), @@ -320,7 +338,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap, periodStartDateApplicableForInterest, detail.getTransactionDate(), - daysInPeriodApplicableForInterest); + daysInPeriodApplicableForInterest, applicableVariations); interestForThisinstallment = principalInterestForThisPeriod.interest(); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); @@ -461,7 +479,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener mc, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap, periodStartDateApplicableForInterest, calculateTill, - daysInPeriodApplicableForInterest); + daysInPeriodApplicableForInterest, applicableVariations); if (!principalInterestForThisPeriod.interest() .plus(principalInterestForThisPeriod.interestPaymentDueToGrace()).plus(outstandingBalance) .isGreaterThan(unprocessed)) { @@ -528,7 +546,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalCumulativePrincipal.minus(reducePrincipal), totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap, - periodStartDateApplicableForInterest, scheduledDueDate, daysInPeriodApplicableForInterest); + periodStartDateApplicableForInterest, scheduledDueDate, daysInPeriodApplicableForInterest, applicableVariations); if (loanApplicationTerms.getFixedEmiAmount() != null && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) { @@ -648,7 +666,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periodNumber, mc, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap, periodStartDateApplicableForInterest, calculateTill, - daysInPeriodApplicableForInterest); + daysInPeriodApplicableForInterest, applicableVariations); Money diff = interestForThisinstallment.minus(interestTillDate.interest()); if (!outstandingBalance.minus(diff).isGreaterThanZero()) { outstandingBalance = outstandingBalance.minus(diff); @@ -860,6 +878,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); int periodNumberTemp = 1; LocalDate lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); + Collection applicableVariations = loanApplicationTerms.getLoanTermVariations(); + do { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, @@ -890,7 +910,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, periodNumberTemp, mc, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap, compoundingMap), compoundingMap, - periodStartDate, detail.getTransactionDate(), daysInPeriod); + periodStartDate, detail.getTransactionDate(), daysInPeriod, applicableVariations); Money interest = principalInterestForThisPeriod.interest(); totalInterest = totalInterest.plus(interest); @@ -922,7 +942,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, periodNumberTemp, mc, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap, compoundingMap), compoundingMap, - periodStartDate, actualRepaymentDate, daysInPeriod); + periodStartDate, actualRepaymentDate, daysInPeriod, applicableVariations); Money interest = principalInterestForThisPeriod.interest(); totalInterest = totalInterest.plus(interest); if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { @@ -959,6 +979,18 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener return applicableTransactions; } + private Collection getApplicableTermVariationsForPeriod(final LocalDate fromDate, final LocalDate dueDate, + final int installmentNumber, final Collection variations) { + Collection applicableVariations = new ArrayList<>(); + for (LoanTermVariationsData detail : variations) { + if (detail.isApplicable(fromDate, dueDate, installmentNumber)) { + applicableVariations.add(detail); + } + } + variations.removeAll(applicableVariations); + return applicableVariations; + } + private List createCurrentTransactionList(RecalculationDetail detail) { List currentTransactions = new ArrayList<>(2); currentTransactions.add(detail.getTransaction()); @@ -1340,7 +1372,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener @Override public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, - final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar) { + final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, + final FloatingRateDTO floatingRateDTO) { final Loan loan = loanRescheduleRequest.getLoan(); final LoanSummary loanSummary = loan.getSummary(); @@ -1482,7 +1515,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // get the loan application terms from the Loan object final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance, - compoundingCalendarInstance, loanCalendar); + compoundingCalendarInstance, loanCalendar, floatingRateDTO); + + // for applying variations + Collection loanTermVariations = new ArrayList<>(loanApplicationTerms.getLoanTermVariations()); // update the number of repayments loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); @@ -1551,12 +1587,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // principal outstanding loanApplicationTerms.setPrincipal(totalPrincipalOutstanding); + // for applying variations + Collection applicableVariations = getApplicableTermVariationsForPeriod(installmentFromDate, + adjustedInstallmentDueDate, periodNumber, loanTermVariations); + // determine the interest & principal for the period PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext, - null, null, installmentFromDate, adjustedInstallmentDueDate, daysInInstallment); + null, null, installmentFromDate, adjustedInstallmentDueDate, daysInInstallment, applicableVariations); // update the interest due for the period period.updateInterestDue(principalInterestForThisPeriod.interest()); @@ -1575,7 +1615,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate, - daysInInstallment); + daysInInstallment, applicableVariations); period.updatePrincipalDue(principalInterestForThisPeriod.principal()); @@ -1642,7 +1682,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap principalVariation, - Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, int daysForInterestInFullPeriod); + Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, int daysForInterestInFullPeriod, + Collection termVariations); protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) { return periodNumber == numberOfRepayments; @@ -1891,8 +1932,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true); outstandingBalance = outstandingBalance.zero().plus(disburseAmt); outstandingBalanceAsPerRest = outstandingBalance; + principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt); } - + // Block process the installment and creates the period if it falls // before reschedule from date // This will create the recalculation details by applying the @@ -1914,7 +1956,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } periodNumber++; } - + for (Map.Entry disburseDetail : disburseDetailMap.entrySet()) { if (disburseDetail.getKey().isAfter(installment.getFromDate()) && !disburseDetail.getKey().isAfter(installment.getDueDate())) { @@ -1926,6 +1968,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // updates actual outstanding balance with new // disbursement detail outstandingBalance = outstandingBalance.plus(disburseDetail.getValue()); + principalToBeScheduled = principalToBeScheduled.plus(disburseDetail.getValue()); } } @@ -2001,12 +2044,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // for partial schedule generation if (!newRepaymentScheduleInstallments.isEmpty()) { - loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO - .createLoanScheduleDTOForPartialUpdate(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, - totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, - totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, - disburseDetailMap, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, - recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate); + loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO.createLoanScheduleDTOForPartialUpdate(periodNumber, + instalmentNumber, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, + totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, reducePrincipal, principalPortionMap, + latePaymentMap, compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, + outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, recalculationDetails, + loanRepaymentScheduleTransactionProcessor, scheduleTillDate); retainedInstallments.addAll(newRepaymentScheduleInstallments); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java index a3bcec953..f72b6e384 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DecliningBalanceInterestLoanScheduleGenerator.java @@ -5,7 +5,10 @@ */ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; +import java.math.BigDecimal; import java.math.MathContext; +import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.TreeMap; @@ -13,6 +16,7 @@ import org.joda.time.Days; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; /** @@ -49,7 +53,8 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS @SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, final TreeMap principalVariation, final Map compoundingMap, - final LocalDate periodStartDate, final LocalDate periodEndDate, final int daysForInterestInFullPeriod) { + final LocalDate periodStartDate, final LocalDate periodEndDate, final int daysForInterestInFullPeriod, + final Collection termVariations) { LocalDate interestStartDate = periodStartDate; Money interestForThisInstallment = totalCumulativePrincipal.zero(); @@ -58,6 +63,20 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS Money balanceForInterestCalculation = outstandingBalance; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays(); + Map interestRates = new HashMap<>(termVariations.size()); + for (LoanTermVariationsData loanTermVariation : termVariations) { + if (loanTermVariation.getTermVariationType().isInterestRateVariation() + && loanTermVariation.isApplicable(periodStartDate, periodEndDate, periodNumber)) { + LocalDate fromDate = loanTermVariation.getTermApplicableFrom(); + if (fromDate == null) { + fromDate = periodStartDate; + } + interestRates.put(fromDate, loanTermVariation.getTermValue()); + if (!principalVariation.containsKey(fromDate)) { + principalVariation.put(fromDate, balanceForInterestCalculation.zero()); + } + } + } if (principalVariation != null) { // identifies rest date after current date for reducing all // compounding @@ -102,6 +121,9 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee); } balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()).plus(compoundFee); + if (interestRates.containsKey(principal.getKey())) { + loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey())); + } } } @@ -125,9 +147,8 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS } else { interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays(daysForInterestInFullPeriod, result .interest().getAmount(), interestForDays)); - cumulatingInterestDueToGrace = cumulatingInterestDueToGrace.plus(calculateInterestForDays( - daysForInterestInFullPeriod, result.interestPaymentDueToGrace().minus(cumulatingInterestDueToGrace) - .getAmount(), interestForDays)); + cumulatingInterestDueToGrace = cumulatingInterestDueToGrace.plus(calculateInterestForDays(daysForInterestInFullPeriod, result + .interestPaymentDueToGrace().minus(cumulatingInterestDueToGrace).getAmount(), interestForDays)); } Money interestForPeriod = interestForThisInstallment; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java index e5a75bf91..38fc72dab 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/FlatInterestLoanScheduleGenerator.java @@ -6,12 +6,14 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.MathContext; +import java.util.Collection; import java.util.Map; import java.util.TreeMap; import org.joda.time.Days; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator { @@ -22,7 +24,7 @@ public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGener final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, @SuppressWarnings("unused") TreeMap principalVariation, @SuppressWarnings("unused") Map compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, - @SuppressWarnings("unused") int daysForInterestInFullPeriod) { + @SuppressWarnings("unused") int daysForInterestInFullPeriod, @SuppressWarnings("unused") Collection termVariations) { final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays(); Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc, null); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 21936b23e..eebdbb911 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; import java.math.MathContext; +import java.util.Collection; import java.util.Date; import java.util.List; @@ -151,6 +152,8 @@ public final class LoanApplicationTerms { private Money approvedPrincipal = null; + private final Collection loanTermVariations; + public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType, @@ -168,7 +171,7 @@ public final class LoanApplicationTerms { final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, - BigDecimal approvedAmount) { + BigDecimal approvedAmount, Collection loanTermVariations) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; @@ -180,7 +183,7 @@ public final class LoanApplicationTerms { disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar, approvedAmount); + installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -195,7 +198,8 @@ public final class LoanApplicationTerms { final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, - final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount) { + final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate, + Collection loanTermVariations) { final Calendar loanCalendar = null; return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek, @@ -203,7 +207,8 @@ public final class LoanApplicationTerms { loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, interestChargedFromDate, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, - loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar, approvedAmount); + loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar, approvedAmount, + annualNominalInterestRate, loanTermVariations); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -218,7 +223,8 @@ public final class LoanApplicationTerms { final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, - final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount) { + final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount, + BigDecimal annualNominalInterestRate, Collection loanTermVariations) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -227,7 +233,6 @@ public final class LoanApplicationTerms { final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); - final BigDecimal annualNominalInterestRate = loanProductRelatedDetail.getAnnualNominalInterestRate(); final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail .getInterestCalculationPeriodMethod(); final Money principalMoney = loanProductRelatedDetail.getPrincipal(); @@ -250,7 +255,7 @@ public final class LoanApplicationTerms { loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount); + loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -263,7 +268,8 @@ public final class LoanApplicationTerms { final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, BigDecimal approvedAmount) { + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, BigDecimal approvedAmount, + final BigDecimal annualNominalInterestRate, final Collection loanTermVariations) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -272,7 +278,6 @@ public final class LoanApplicationTerms { final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); - final BigDecimal annualNominalInterestRate = loanProductRelatedDetail.getAnnualNominalInterestRate(); final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail .getInterestCalculationPeriodMethod(); final Money principalMoney = loanProductRelatedDetail.getPrincipal(); @@ -302,28 +307,7 @@ public final class LoanApplicationTerms { daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount); - } - - public static LoanApplicationTerms assembleFrom(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, - final LoanApplicationTerms applicationTerms, BigDecimal approvedAmount) { - return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency, - applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, repaymentEvery, - repaymentPeriodFrequencyType, null, null, applicationTerms.amortizationMethod, applicationTerms.interestMethod, - applicationTerms.interestRatePerPeriod, applicationTerms.interestRatePeriodFrequencyType, - applicationTerms.annualNominalInterestRate, applicationTerms.interestCalculationPeriodMethod, applicationTerms.principal, - applicationTerms.expectedDisbursementDate, applicationTerms.repaymentsStartingFromDate, - applicationTerms.calculatedRepaymentsStartingFromDate, applicationTerms.principalGrace, - applicationTerms.interestPaymentGrace, applicationTerms.interestChargingGrace, applicationTerms.interestChargedFromDate, - applicationTerms.inArrearsTolerance, applicationTerms.multiDisburseLoan, applicationTerms.fixedEmiAmount, - applicationTerms.disbursementDatas, applicationTerms.maxOutstandingBalance, applicationTerms.emiAmountVariations, - applicationTerms.graceOnArrearsAgeing, applicationTerms.daysInMonthType, applicationTerms.daysInYearType, - applicationTerms.interestRecalculationEnabled, applicationTerms.rescheduleStrategyMethod, - applicationTerms.interestRecalculationCompoundingMethod, applicationTerms.restCalendarInstance, - applicationTerms.recalculationFrequencyType, applicationTerms.compoundingCalendarInstance, - applicationTerms.compoundingFrequencyType, applicationTerms.principalThresholdForLastInstalment, - applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy, - applicationTerms.loanCalendar, approvedAmount); + loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -344,7 +328,7 @@ public final class LoanApplicationTerms { final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, - BigDecimal approvedAmount) { + BigDecimal approvedAmount, Collection loanTermVariations) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; @@ -394,6 +378,7 @@ public final class LoanApplicationTerms { this.loanCalendar = loanCalendar; this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount); + this.loanTermVariations = loanTermVariations; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -1215,10 +1200,6 @@ public final class LoanApplicationTerms { } } - public BigDecimal getInterestRatePerPeriod() { - return this.interestRatePerPeriod; - } - public BigDecimal getAnnualNominalInterestRate() { return this.annualNominalInterestRate; } @@ -1270,9 +1251,12 @@ public final class LoanApplicationTerms { return loanCalendar; } - public BigDecimal getFixedPrincipalAmount() { return this.fixedPrincipalAmount; } + public Collection getLoanTermVariations() { + return this.loanTermVariations; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java index 7db2db481..72ee9b1d3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java @@ -14,6 +14,7 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -38,5 +39,5 @@ public interface LoanScheduleGenerator { LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, - CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar); + CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, FloatingRateDTO floatingRateDTO); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index fea999b02..2d6136139 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -9,6 +9,7 @@ import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Set; @@ -17,6 +18,7 @@ import org.apache.commons.lang.StringUtils; import org.joda.time.LocalDate; import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; +import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.holiday.domain.HolidayRepository; import org.mifosplatform.organisation.holiday.domain.HolidayStatusType; @@ -45,6 +47,9 @@ import org.mifosplatform.portfolio.common.domain.DayOfWeekType; import org.mifosplatform.portfolio.common.domain.DaysInMonthType; import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; @@ -99,6 +104,7 @@ public class LoanScheduleAssembler { private final ClientRepositoryWrapper clientRepository; private final GroupRepositoryWrapper groupRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; @Autowired public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository, @@ -107,7 +113,8 @@ public class LoanScheduleAssembler { final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository, final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository, - final WorkingDaysRepositoryWrapper workingDaysRepository) { + final WorkingDaysRepositoryWrapper workingDaysRepository, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { this.fromApiJsonHelper = fromApiJsonHelper; this.loanProductRepository = loanProductRepository; this.applicationCurrencyRepository = applicationCurrencyRepository; @@ -120,6 +127,7 @@ public class LoanScheduleAssembler { this.clientRepository = clientRepository; this.groupRepository = groupRepository; this.workingDaysRepository = workingDaysRepository; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; } public LoanApplicationTerms assembleLoanTerms(final JsonElement element) { @@ -164,8 +172,10 @@ public class LoanScheduleAssembler { final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); final PeriodFrequencyType interestRatePeriodFrequencyType = loanProduct.getInterestPeriodFrequencyType(); - final BigDecimal annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, - interestRatePerPeriod); + BigDecimal annualNominalInterestRate = BigDecimal.ZERO; + if (interestRatePerPeriod != null) { + annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, interestRatePerPeriod); + } // disbursement details final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); @@ -296,6 +306,31 @@ public class LoanScheduleAssembler { final Integer installmentAmountInMultiplesOf = loanProduct.getInstallmentAmountInMultiplesOf(); + Collection loanTermVariations = new ArrayList<>(); + if (loanProduct.isLinkedToFloatingInterestRate()) { + final BigDecimal interestRateDiff = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( + LoanApiConstants.interestRateDifferentialParameterName, element); + final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed( + LoanApiConstants.isFloatingInterestRateParameterName, element); + List baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() + .getRatePeriods(); + FloatingRateDTO floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, expectedDisbursementDate, interestRateDiff, + baseLendingRatePeriods); + Collection applicableRates = loanProduct.fetchInterestRates(floatingRateDTO); + + LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant(); + for (FloatingRatePeriodData periodData : applicableRates) { + LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( + LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), + periodData.getInterestRate()); + if (interestRateStartDate.isAfter(periodData.getFromDateAsLocalDate())) { + interestRateStartDate = periodData.getFromDateAsLocalDate(); + annualNominalInterestRate = periodData.getInterestRate(); + } + loanTermVariations.add(loanTermVariation); + } + } + return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, @@ -304,7 +339,8 @@ public class LoanScheduleAssembler { loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO); + installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO, + loanTermVariations); } private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index 5ab734c27..7f11bbeb8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -26,6 +26,9 @@ import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; @@ -63,6 +66,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC private final CalendarInstanceRepository calendarInstanceRepository; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final ConfigurationDomainService configurationDomainService; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; @Autowired public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer, @@ -72,7 +76,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC final LoanAssembler loanAssembler, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, final LoanAccountDomainService accountDomainService, final CalendarInstanceRepository calendarInstanceRepository, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, - final ConfigurationDomainService configurationDomainService) { + final ConfigurationDomainService configurationDomainService, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.loanScheduleAssembler = loanScheduleAssembler; this.fromJsonHelper = fromJsonHelper; @@ -86,6 +91,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC this.calendarInstanceRepository = calendarInstanceRepository; this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; this.configurationDomainService = configurationDomainService; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; } @Override @@ -164,11 +170,12 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); LocalDate calculatedRepaymentsStartingFromDate = accountDomainService.getCalculatedRepaymentsStartingFromDate( loan.getDisbursementDate(), loan, calendarInstance); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency, - calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance); - LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount( - currency, today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), - loanRepaymentScheduleTransactionProcessor); + calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance, floatingRateDTO); + LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency, + today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), + loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor); Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus( loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); Money interestDue = Money.zero(currency); @@ -209,4 +216,17 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC } loanScheduleData.updateFuturePeriods(futureInstallments); } + + private FloatingRateDTO constructFloatingRateDTO(final Loan loan) { + FloatingRateDTO floatingRateDTO = null; + if (loan.loanProduct().isLinkedToFloatingInterestRate()) { + boolean isFloatingInterestRate = loan.getIsFloatingInterestRate(); + BigDecimal interestRateDiff = loan.getInterestRateDifferential(); + List baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() + .getRatePeriods(); + floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff, + baseLendingRatePeriods); + } + return floatingRateDTO; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java index be00e9f85..0f183076f 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java @@ -10,6 +10,7 @@ import java.math.MathContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator; @@ -21,19 +22,21 @@ public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory { public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance, - final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar) { + final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, final FloatingRateDTO floatingRateDTO) { LoanRescheduleModel loanRescheduleModel = null; switch (interestMethod) { case DECLINING_BALANCE: loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, - applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar); + applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar, + floatingRateDTO); break; case FLAT: loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, - applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar); + applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar, + floatingRateDTO); break; case INVALID: diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java index af09529d8..6a1a47b3c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java @@ -10,6 +10,7 @@ import java.math.MathContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -18,5 +19,5 @@ public interface LoanReschedulerFactory { public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, - final Calendar loanCalendar); + final Calendar loanCalendar, FloatingRateDTO floatingRateDTO); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index 3d37e656f..a41c254ed 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -5,6 +5,7 @@ */ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service; +import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.List; @@ -23,6 +24,9 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; @@ -47,6 +51,7 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP private final WorkingDaysRepositoryWrapper workingDaysRepository; private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; private final CalendarInstanceRepository calendarInstanceRepository; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; @Autowired public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository, @@ -54,7 +59,8 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, - final CalendarInstanceRepository calendarInstanceRepository) { + final CalendarInstanceRepository calendarInstanceRepository, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { this.loanRescheduleRequestRepository = loanRescheduleRequestRepository; this.applicationCurrencyRepository = applicationCurrencyRepository; this.configurationDomainService = configurationDomainService; @@ -62,6 +68,7 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP this.workingDaysRepository = workingDaysRepository; this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService; this.calendarInstanceRepository = calendarInstanceRepository; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; } @Override @@ -73,8 +80,8 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP Loan loan = loanRescheduleRequest.getLoan(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), - loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan + .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency(); @@ -83,16 +90,16 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod(); final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final MathContext mathContext = new MathContext(8, roundingMode); - List oldPeriods = this.loanScheduleHistoryWritePlatformService - .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); + List oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive( + loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); CalendarInstance restCalendarInstance = null; CalendarInstance compoundingCalendarInstance = null; if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); - compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); @@ -100,12 +107,26 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP if (loanCalendarInstance != null) { loanCalendar = loanCalendarInstance.getCalendar(); } + final FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, - loanCalendar); + loanCalendar, floatingRateDTO); LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel, oldPeriods); return loanRescheduleModelWithOldPeriods; } + private FloatingRateDTO constructFloatingRateDTO(final Loan loan) { + FloatingRateDTO floatingRateDTO = null; + if (loan.loanProduct().isLinkedToFloatingInterestRate()) { + boolean isFloatingInterestRate = loan.getIsFloatingInterestRate(); + BigDecimal interestRateDiff = loan.getInterestRateDifferential(); + List baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() + .getRatePeriods(); + floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff, + baseLendingRatePeriods); + } + return floatingRateDTO; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index eb698ff4c..b63b88ce5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -36,6 +36,9 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidByData; import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; @@ -84,6 +87,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final LoanRepository loanRepository; private final LoanAssembler loanAssembler; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; /** * LoanRescheduleRequestWritePlatformServiceImpl constructor @@ -103,7 +107,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanTransactionRepository loanTransactionRepository, final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepository loanRepository, - final LoanAssembler loanAssembler) { + final LoanAssembler loanAssembler, final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { this.loanRepositoryWrapper = loanRepositoryWrapper; this.codeValueRepositoryWrapper = codeValueRepositoryWrapper; this.platformSecurityContext = platformSecurityContext; @@ -123,6 +127,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.loanRepository = loanRepository; this.loanAssembler = loanAssembler; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; } /** @@ -272,8 +277,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche final Map changes = new LinkedHashMap<>(); LocalDate approvedOnDate = jsonCommand.localDateValueOfParameterNamed("approvedOnDate"); - final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()) - .withLocale(jsonCommand.extractLocale()); + final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale( + jsonCommand.extractLocale()); changes.put("locale", jsonCommand.locale()); changes.put("dateFormat", jsonCommand.dateFormat()); @@ -285,8 +290,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche final LoanSummary loanSummary = loan.getSummary(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), - loan.getDisbursementDate().toDate()); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan + .getDisbursementDate().toDate()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency(); @@ -314,9 +319,10 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche if (loanCalendarInstance != null) { loanCalendar = loanCalendarInstance.getCalendar(); } + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, - loanCalendar); + loanCalendar, floatingRateDTO); final Collection periods = loanRescheduleModel.getPeriods(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); @@ -373,7 +379,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche waiveLoanCharges(loan, waiveLoanCharges); // update the Loan summary - loanSummary.updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true, null); + loanSummary + .updateSummary(currency, loan.getPrincpal(), repaymentScheduleInstallments, new LoanSummaryWrapper(), true, null); // update the total number of schedule repayments loan.updateNumberOfRepayments(periods.size()); @@ -458,26 +465,27 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); - calculatedRepaymentsStartingFromDate = this.loanAccountDomainService - .getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, calendarInstance); + calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + loan.getDisbursementDate(), loan, calendarInstance); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), - loan.getDisbursementDate().toDate()); + holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate() + .toDate()); workingDays = this.workingDaysRepository.findOne(); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); - recalculateFrom = DateUtils.getLocalDateOfTenant(); + recalculateFrom = DateUtils.getLocalDateOfTenant(); } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, - recalculateFrom, overdurPenaltyWaitPeriod); + recalculateFrom, overdurPenaltyWaitPeriod, floatingRateDTO); Money accruedCharge = Money.zero(loan.getCurrency()); if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { - Collection chargePaidByDatas = this.loanChargeReadPlatformService - .retriveLoanChargesPaidBy(loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber); + Collection chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy( + loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber); for (LoanChargePaidByData chargePaidByData : chargePaidByDatas) { accruedCharge = accruedCharge.plus(chargePaidByData.getAmount()); } @@ -527,8 +535,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche final Map changes = new LinkedHashMap<>(); LocalDate rejectedOnDate = jsonCommand.localDateValueOfParameterNamed("rejectedOnDate"); - final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()) - .withLocale(jsonCommand.extractLocale()); + final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale( + jsonCommand.extractLocale()); changes.put("locale", jsonCommand.locale()); changes.put("dateFormat", jsonCommand.dateFormat()); @@ -567,4 +575,17 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche throw new PlatformDataIntegrityException("error.msg.loan.reschedule.unknown.data.integrity.issue", "Unknown data integrity issue with resource."); } + + private FloatingRateDTO constructFloatingRateDTO(final Loan loan) { + FloatingRateDTO floatingRateDTO = null; + if (loan.loanProduct().isLinkedToFloatingInterestRate()) { + boolean isFloatingInterestRate = loan.getIsFloatingInterestRate(); + BigDecimal interestRateDiff = loan.getInterestRateDifferential(); + List baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() + .getRatePeriods(); + floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff, + baseLendingRatePeriods); + } + return floatingRateDTO; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java index dcbea159b..a579768f7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -36,12 +36,14 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper { final Set supportedParameters = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanType", "calendarId", "productId", "accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId", "principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency", - "loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "interestRateFrequencyType", - "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment", "graceOnInterestPayment", - "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", "charges", - "collateral", "syncDisbursementWithMeeting", "linkAccountId", LoanApiConstants.disbursementDataParameterName, - LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, - LoanProductConstants.graceOnArrearsAgeingParameterName, LoanProductConstants.recalculationRestFrequencyDateParamName,"createStandingInstructionAtDisbursement")); + "loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", + "interestRateFrequencyType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment", + "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", + "dateFormat", "charges", "collateral", "syncDisbursementWithMeeting", "linkAccountId", + LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, + LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName, + LoanProductConstants.recalculationRestFrequencyDateParamName, "createStandingInstructionAtDisbursement", + LoanApiConstants.isFloatingInterestRateParameterName, LoanApiConstants.interestRateDifferentialParameterName)); private final FromJsonHelper fromApiJsonHelper; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 93e1abfff..8d64a9f0c 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -48,10 +48,10 @@ public final class LoanApplicationCommandFromApiJsonHelper { */ final Set supportedParameters = new HashSet<>(Arrays.asList("dateFormat", "locale", "id", "clientId", "groupId", "loanType", "productId", "principal", "loanTermFrequency", "loanTermFrequencyType", "numberOfRepayments", "repaymentEvery", - "repaymentFrequencyType", "repaymentFrequencyNthDayType", "repaymentFrequencyDayOfWeekType", - "interestRatePerPeriod", - "amortizationType", - "interestType", + "repaymentFrequencyType", "repaymentFrequencyNthDayType", "repaymentFrequencyDayOfWeekType", "interestRatePerPeriod", + "amortizationType", "interestType", + LoanApiConstants.isFloatingInterestRate, + LoanApiConstants.interestRateDifferential, "interestCalculationPeriodType", "interestRateFrequencyType", "expectedDisbursementDate", @@ -203,12 +203,6 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull() .inMinMaxRange(0, 3); - final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; - final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( - interestRatePerPeriodParameterName, element); - baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull() - .zeroOrPositiveAmount(); - final String interestTypeParameterName = "interestType"; final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element); baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1); @@ -219,6 +213,71 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(interestCalculationPeriodTypeParameterName).value(interestCalculationPeriodType).notNull() .inMinMaxRange(0, 1); + if (loanProduct.isLinkedToFloatingInterestRate()) { + if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) { + baseDataValidator + .reset() + .parameter("interestRatePerPeriod") + .failWithCode("not.supported.loanproduct.linked.to.floating.rate", + "interestRatePerPeriod param is not supported, selected Loan Product is linked with floating interest rate."); + } + + if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)){ + final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed( + LoanApiConstants.isFloatingInterestRate, element); + if(isFloatingInterestRate != null && isFloatingInterestRate && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()){ + baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode("true.not.supported.for.selected.loanproduct", + "isFloatingInterestRate value of true not supported for selected Loan Product."); + } + } else { + baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false); + } + + if (interestType != null && interestType == InterestMethod.FLAT.getValue()) { + baseDataValidator + .reset() + .parameter(interestTypeParameterName) + .failWithCode("should.be.0.for.selected.loan.product", + "interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates."); + } + + final String interestRateDifferentialParameterName = LoanApiConstants.interestRateDifferential; + final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( + interestRateDifferentialParameterName, element); + baseDataValidator + .reset() + .parameter(interestRateDifferentialParameterName) + .value(interestRateDifferential) + .notNull() + .zeroOrPositiveAmount() + .inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(), + loanProduct.getFloatingRates().getMaxDifferentialLendingRate()); + + } else { + + if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) { + baseDataValidator + .reset() + .parameter(LoanApiConstants.isFloatingInterestRate) + .failWithCode("not.supported.loanproduct.not.linked.to.floating.rate", + "isFloatingInterestRate param is not supported, selected Loan Product is not linked with floating interest rate."); + } + if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferential, element)) { + baseDataValidator + .reset() + .parameter(LoanApiConstants.interestRateDifferential) + .failWithCode("not.supported.loanproduct.not.linked.to.floating.rate", + "interestRateDifferential param is not supported, selected Loan Product is not linked with floating interest rate."); + } + + final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; + final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( + interestRatePerPeriodParameterName, element); + baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull() + .zeroOrPositiveAmount(); + + } + final String amortizationTypeParameterName = "amortizationType"; final Integer amortizationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(amortizationTypeParameterName, element); baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1); @@ -392,7 +451,7 @@ public final class LoanApplicationCommandFromApiJsonHelper { if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } - public void validateForModify(final String json, final LoanProduct loanProduct) { + public void validateForModify(final String json, final LoanProduct loanProduct, final Loan existingLoanApplication) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } final Type typeOfMap = new TypeToken>() {}.getType(); @@ -518,22 +577,93 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(repaymentEveryTypeParameterName).value(repaymentEveryType).notNull().inMinMaxRange(0, 3); } - final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; - if (this.fromApiJsonHelper.parameterExists(interestRatePerPeriodParameterName, element)) { - atLeastOneParameterPassedForUpdate = true; - final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( - interestRatePerPeriodParameterName, element); - baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull() - .zeroOrPositiveAmount(); - } - final String interestTypeParameterName = "interestType"; + Integer interestType = null; if (this.fromApiJsonHelper.parameterExists(interestTypeParameterName, element)) { atLeastOneParameterPassedForUpdate = true; - final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(interestTypeParameterName, element); + interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(interestTypeParameterName, element); baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1); } + if (loanProduct.isLinkedToFloatingInterestRate()) { + if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) { + baseDataValidator + .reset() + .parameter("interestRatePerPeriod") + .failWithCode("not.supported.loanproduct.linked.to.floating.rate", + "interestRatePerPeriod param is not supported, selected Loan Product is linked with floating interest rate."); + } + + Boolean isFloatingInterestRate = existingLoanApplication.getIsFloatingInterestRate(); + if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)){ + isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, element); + atLeastOneParameterPassedForUpdate = true; + } + if(isFloatingInterestRate != null){ + if(isFloatingInterestRate + && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()){ + baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode("true.not.supported.for.selected.loanproduct", + "isFloatingInterestRate value of true not supported for selected Loan Product."); + } + } else { + baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false); + } + + if (interestType == null) { + interestType = existingLoanApplication.getLoanProductRelatedDetail().getInterestMethod().getValue(); + } + if (interestType != null && interestType == InterestMethod.FLAT.getValue()) { + baseDataValidator + .reset() + .parameter(interestTypeParameterName) + .failWithCode("should.be.0.for.selected.loan.product", + "interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates."); + } + + final String interestRateDifferentialParameterName = LoanApiConstants.interestRateDifferential; + BigDecimal interestRateDifferential = existingLoanApplication.getInterestRateDifferential(); + if (this.fromApiJsonHelper.parameterExists(interestRateDifferentialParameterName, element)) { + interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRateDifferentialParameterName, + element); + atLeastOneParameterPassedForUpdate = true; + } + baseDataValidator + .reset() + .parameter(interestRateDifferentialParameterName) + .value(interestRateDifferential) + .notNull() + .zeroOrPositiveAmount() + .inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(), + loanProduct.getFloatingRates().getMaxDifferentialLendingRate()); + + } else { + + if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) { + baseDataValidator + .reset() + .parameter(LoanApiConstants.isFloatingInterestRate) + .failWithCode("not.supported.loanproduct.not.linked.to.floating.rate", + "isFloatingInterestRate param is not supported, selected Loan Product is not linked with floating interest rate."); + } + if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferential, element)) { + baseDataValidator + .reset() + .parameter(LoanApiConstants.interestRateDifferential) + .failWithCode("not.supported.loanproduct.not.linked.to.floating.rate", + "interestRateDifferential param is not supported, selected Loan Product is not linked with floating interest rate."); + } + + final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; + BigDecimal interestRatePerPeriod = existingLoanApplication.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod(); + if (this.fromApiJsonHelper.parameterExists(interestRatePerPeriodParameterName, element)) { + this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(interestRatePerPeriodParameterName, element); + atLeastOneParameterPassedForUpdate = true; + } + baseDataValidator.reset().parameter(interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull() + .zeroOrPositiveAmount(); + + } + final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType"; if (this.fromApiJsonHelper.parameterExists(interestCalculationPeriodTypeParameterName, element)) { atLeastOneParameterPassedForUpdate = true; @@ -849,23 +979,23 @@ public final class LoanApplicationCommandFromApiJsonHelper { if (variationArray != null) { for (int i = 0; i < variationArray.size(); i++) { final JsonObject jsonObject1 = variationArray.get(i).getAsJsonObject(); - if(jsonObject1.has(LoanApiConstants.disbursementDateParameterName)){ - LocalDate date1 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, jsonObject1, - dateFormat, locale); + if (jsonObject1.has(LoanApiConstants.disbursementDateParameterName)) { + LocalDate date1 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, + jsonObject1, dateFormat, locale); for (int j = i + 1; j < variationArray.size(); j++) { final JsonObject jsonObject2 = variationArray.get(j).getAsJsonObject(); - if(jsonObject2.has(LoanApiConstants.disbursementDateParameterName)){ + if (jsonObject2.has(LoanApiConstants.disbursementDateParameterName)) { LocalDate date2 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, jsonObject2, dateFormat, locale); if (date1.isAfter(date2)) { baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName) .failWithCode(LoanApiConstants.DISBURSEMENT_DATES_NOT_IN_ORDER); } - } + } } } - + } } } @@ -918,8 +1048,9 @@ public final class LoanApplicationCommandFromApiJsonHelper { if (expectedDisbursement.equals(expectedDisbursementDate)) { isFirstinstallmentOnExpectedDisbursementDate = true; } - - }i++; + + } + i++; } while (i < variationArray.size()); if (!isFirstinstallmentOnExpectedDisbursementDate) { baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index d2e008764..89fcb8f0f 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -472,13 +472,23 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa try { AppUser currentUser = getAppUserIfPresent(); final Loan existingLoanApplication = retrieveLoanBy(loanId); - this.fromApiJsonDeserializer.validateForModify(command.json(), existingLoanApplication.loanProduct()); - - checkClientOrGroupActive(existingLoanApplication); - if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified( loanId); } + final String productIdParamName = "productId"; + LoanProduct newLoanProduct = null; + if (command.isChangeInLongParameterNamed(productIdParamName, existingLoanApplication.loanProduct().getId())) { + final Long productId = command.longValueOfParameterNamed(productIdParamName); + newLoanProduct = this.loanProductRepository.findOne(productId); + if (newLoanProduct == null) { throw new LoanProductNotFoundException(productId); } + } + + LoanProduct loanProductForValidations = newLoanProduct == null? existingLoanApplication.loanProduct(): newLoanProduct; + + this.fromApiJsonDeserializer.validateForModify(command.json(), loanProductForValidations, existingLoanApplication); + + checkClientOrGroupActive(existingLoanApplication); + final Set existingCharges = existingLoanApplication.charges(); Map chargesMap = new HashMap<>(); for (LoanCharge charge : existingCharges) { @@ -554,33 +564,34 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa existingLoanApplication.updateGroup(group); } - final String productIdParamName = "productId"; - if (changes.containsKey(productIdParamName)) { - final Long productId = command.longValueOfParameterNamed(productIdParamName); - final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); - if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } - - existingLoanApplication.updateLoanProduct(loanProduct); + if (newLoanProduct != null) { + existingLoanApplication.updateLoanProduct(newLoanProduct); if (!changes.containsKey("interestRateFrequencyType")) { existingLoanApplication.updateInterestRateFrequencyType(); } final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); - if (loanProduct.useBorrowerCycle()) { + if (newLoanProduct.useBorrowerCycle()) { final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", command.parsedJson()); final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", command.parsedJson()); Integer cycleNumber = 0; if (clientId != null) { - cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, loanProduct.getId()); + cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, newLoanProduct.getId()); } else if (groupId != null) { cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(), - loanProduct.getId()); + newLoanProduct.getId()); } this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, - loanProduct, cycleNumber); + newLoanProduct, cycleNumber); } else { this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, - loanProduct); + newLoanProduct); + } + if(newLoanProduct.isLinkedToFloatingInterestRate()){ + existingLoanApplication.getLoanProductRelatedDetail().updateForFloatingInterestRates(); + }else{ + existingLoanApplication.setInterestRateDifferential(null); + existingLoanApplication.setIsFloatingInterestRate(null); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index ceda41de4..74bd0b16a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -215,6 +215,9 @@ public class LoanAssembler { Group group = null; final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element); + + final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element); + final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element); final String loanTypeParameterName = "loanType"; final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element); @@ -239,20 +242,21 @@ public class LoanAssembler { loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, - createStandingInstructionAtDisbursement); + createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential); } else if (group != null) { loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, - createStandingInstructionAtDisbursement); + createStandingInstructionAtDisbursement,isFloatingInterestRate, interestRateDifferential); } else if (client != null) { loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, - fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement); + fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, + isFloatingInterestRate, interestRateDifferential); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java index 684f3c3e7..e1f49a61a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -14,6 +14,7 @@ import org.mifosplatform.infrastructure.core.service.Page; import org.mifosplatform.infrastructure.core.service.SearchParameters; import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.portfolio.calendar.data.CalendarData; +import org.mifosplatform.portfolio.floatingrates.data.InterestRatePeriodData; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData; import org.mifosplatform.portfolio.loanaccount.data.LoanApprovalData; @@ -95,7 +96,7 @@ public interface LoanReadPlatformService { Collection retrivePeriodicAccrualData(LocalDate tillDate); - Collection fetchArrearLoans(); + Collection fetchLoansForInterestRecalculation(); LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate onDate); @@ -110,4 +111,6 @@ public interface LoanReadPlatformService { PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId); LoanTransactionData retrieveRefundByCashTemplate(Long loanId); + + Collection retrieveLoanInterestRatePeriodData(Long loanId); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 4cb0fd145..68def0c3a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.loanaccount.service; +import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestType; + import java.math.BigDecimal; import java.sql.ResultSet; import java.sql.SQLException; @@ -61,6 +63,10 @@ import org.mifosplatform.portfolio.client.domain.ClientEnumerations; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.service.CommonEnumerations; +import org.mifosplatform.portfolio.floatingrates.data.InterestRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; @@ -97,6 +103,7 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanSche import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData; +import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.service.LoanDropdownReadPlatformService; import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService; @@ -142,6 +149,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private final WorkingDaysRepositoryWrapper workingDaysRepository; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; @Autowired public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository, @@ -155,7 +163,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository, final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, final WorkingDaysRepositoryWrapper workingDaysRepository, PaymentTypeReadPlatformService paymentTypeReadPlatformService, - final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) { + final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { this.context = context; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -178,6 +187,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.workingDaysRepository = workingDaysRepository; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; } @Override @@ -197,8 +207,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { sqlBuilder.append(" left join m_office transferToOffice on transferToOffice.id = c.transfer_to_office_id "); sqlBuilder.append(" where l.id=? and ( o.hierarchy like ? or transferToOffice.hierarchy like ?)"); - return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), rm, - new Object[] { loanId, hierarchySearchString, hierarchySearchString }); + return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), rm, new Object[] { loanId, hierarchySearchString, + hierarchySearchString }); } catch (final EmptyResultDataAccessException e) { throw new LoanNotFoundException(loanId); } @@ -236,7 +246,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { /*** * TODO Vishwas: Remove references to "Contra" from the codebase ***/ - final String sql = "select " + rm.LoanPaymentsSchema() + final String sql = "select " + + rm.LoanPaymentsSchema() + " where tr.loan_id = ? and tr.transaction_type_enum not in (0, 3) and (tr.is_reversed=0 or tr.manually_adjusted_or_reversed = 1) order by tr.transaction_date ASC,id "; return this.jdbcTemplate.query(sql, rm, new Object[] { loanId }); } catch (final EmptyResultDataAccessException e) { @@ -395,10 +406,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final BigDecimal outstandingLoanBalance = null; final BigDecimal unrecognizedIncomePortion = null; return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, - loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), - loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(), - loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(), - loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(), + loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment + .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency) + .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion, paymentOptions, null, null, null, outstandingLoanBalance, false); } @@ -425,26 +435,26 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); - compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), - CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); + compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( + loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), - loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); + final List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan + .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); } + final FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(this.loanScheduleFactory, - restCalendarInstance, compoundingCalendarInstance, holidayDetailDTO, onDate); + restCalendarInstance, compoundingCalendarInstance, holidayDetailDTO, onDate, floatingRateDTO); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT); final Collection paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(); final BigDecimal unrecognizedIncomePortion = null; return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, - loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), - loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(), - loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(), - loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(), + loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment + .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency) + .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion, paymentOptions, null, null, null, outstandingLoanBalance, false); } @@ -544,6 +554,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { return "l.id as id, l.account_no as accountNo, l.external_id as externalId, l.fund_id as fundId, f.name as fundName," + " l.loan_type_enum as loanType, l.loanpurpose_cv_id as loanPurposeId, cv.code_value as loanPurposeName," + " lp.id as loanProductId, lp.name as loanProductName, lp.description as loanProductDescription," + + " lp.is_linked_to_floating_interest_rates as isLoanProductLinkedToFloatingRate, " + " lp.allow_multiple_disbursals as multiDisburseLoan," + " lp.can_define_fixed_emi_amount as canDefineInstallmentAmount," + " c.id as clientId, c.display_name as clientName, c.office_id as clientOfficeId," @@ -568,14 +579,19 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " lps.name as transactionStrategyName, " + " l.currency_code as currencyCode, l.currency_digits as currencyDigits, l.currency_multiplesof as inMultiplesOf, rc.`name` as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, " + " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName, " - + " l.principal_disbursed_derived as principalDisbursed," + " l.principal_repaid_derived as principalPaid," + + " l.principal_disbursed_derived as principalDisbursed," + + " l.principal_repaid_derived as principalPaid," + " l.principal_writtenoff_derived as principalWrittenOff," - + " l.principal_outstanding_derived as principalOutstanding," + " l.interest_charged_derived as interestCharged," - + " l.interest_repaid_derived as interestPaid," + " l.interest_waived_derived as interestWaived," - + " l.interest_writtenoff_derived as interestWrittenOff," + " l.interest_outstanding_derived as interestOutstanding," + + " l.principal_outstanding_derived as principalOutstanding," + + " l.interest_charged_derived as interestCharged," + + " l.interest_repaid_derived as interestPaid," + + " l.interest_waived_derived as interestWaived," + + " l.interest_writtenoff_derived as interestWrittenOff," + + " l.interest_outstanding_derived as interestOutstanding," + " l.fee_charges_charged_derived as feeChargesCharged," + " l.total_charges_due_at_disbursement_derived as feeChargesDueAtDisbursementCharged," - + " l.fee_charges_repaid_derived as feeChargesPaid," + " l.fee_charges_waived_derived as feeChargesWaived," + + " l.fee_charges_repaid_derived as feeChargesPaid," + + " l.fee_charges_waived_derived as feeChargesWaived," + " l.fee_charges_writtenoff_derived as feeChargesWrittenOff," + " l.fee_charges_outstanding_derived as feeChargesOutstanding," + " l.penalty_charges_charged_derived as penaltyChargesCharged," @@ -583,14 +599,21 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " l.penalty_charges_waived_derived as penaltyChargesWaived," + " l.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff," + " l.penalty_charges_outstanding_derived as penaltyChargesOutstanding," - + " l.total_expected_repayment_derived as totalExpectedRepayment," + " l.total_repayment_derived as totalRepayment," - + " l.total_expected_costofloan_derived as totalExpectedCostOfLoan," + " l.total_costofloan_derived as totalCostOfLoan," - + " l.total_waived_derived as totalWaived," + " l.total_writtenoff_derived as totalWrittenOff," - + " l.total_outstanding_derived as totalOutstanding," + " l.total_overpaid_derived as totalOverpaid," - + " l.fixed_emi_amount as fixedEmiAmount," + " l.max_outstanding_loan_balance as outstandingLoanBalance," - + " la.principal_overdue_derived as principalOverdue," + " la.interest_overdue_derived as interestOverdue," + + " l.total_expected_repayment_derived as totalExpectedRepayment," + + " l.total_repayment_derived as totalRepayment," + + " l.total_expected_costofloan_derived as totalExpectedCostOfLoan," + + " l.total_costofloan_derived as totalCostOfLoan," + + " l.total_waived_derived as totalWaived," + + " l.total_writtenoff_derived as totalWrittenOff," + + " l.total_outstanding_derived as totalOutstanding," + + " l.total_overpaid_derived as totalOverpaid," + + " l.fixed_emi_amount as fixedEmiAmount," + + " l.max_outstanding_loan_balance as outstandingLoanBalance," + + " la.principal_overdue_derived as principalOverdue," + + " la.interest_overdue_derived as interestOverdue," + " la.fee_charges_overdue_derived as feeChargesOverdue," - + " la.penalty_charges_overdue_derived as penaltyChargesOverdue," + " la.total_overdue_derived as totalOverdue," + + " la.penalty_charges_overdue_derived as penaltyChargesOverdue," + + " la.total_overdue_derived as totalOverdue," + " la.overdue_since_date_derived as overdueSinceDate," + " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting," + " l.loan_counter as loanCounter, l.loan_product_counter as loanProductCounter," @@ -601,7 +624,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " lir.rest_freqency_date as restFrequencyDate, " + " lir.compounding_frequency_type_enum as compoundingFrequencyEnum, lir.compounding_frequency_interval as compoundingInterval, " + " lir.compounding_freqency_date as compoundingFrequencyDate, " - + " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement " + " from m_loan l" // + + " l.is_floating_interest_rate as isFloatingInterestRate, " + + " l.interest_rate_differential as interestRateDifferential, " + + " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement " + + " from m_loan l" // + " join m_product_loan lp on lp.id = l.product_id" // + " left join m_loan_recalculation_details lir on lir.loan_id = l.id " + " join m_currency rc on rc.`code` = l.currency_code" // @@ -614,7 +640,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " left join m_appuser rbu on rbu.id = l.rejectedon_userid" + " left join m_appuser wbu on wbu.id = l.withdrawnon_userid" + " left join m_appuser abu on abu.id = l.approvedon_userid" - + " left join m_appuser dbu on dbu.id = l.disbursedon_userid" + " left join m_appuser cbu on cbu.id = l.closedon_userid" + + " left join m_appuser dbu on dbu.id = l.disbursedon_userid" + + " left join m_appuser cbu on cbu.id = l.closedon_userid" + " left join m_code_value cv on cv.id = l.loanpurpose_cv_id" + " left join ref_loan_transaction_processing_strategy lps on lps.id = l.loan_transaction_strategy_id"; } @@ -664,6 +691,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Long loanProductId = JdbcSupport.getLong(rs, "loanProductId"); final String loanProductName = rs.getString("loanProductName"); final String loanProductDescription = rs.getString("loanProductDescription"); + final boolean isLoanProductLinkedToFloatingRate = rs.getBoolean("isLoanProductLinkedToFloatingRate"); final Boolean multiDisburseLoan = rs.getBoolean("multiDisburseLoan"); final Boolean canDefineInstallmentAmount = rs.getBoolean("canDefineInstallmentAmount"); final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance"); @@ -720,6 +748,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaymentEvery"); final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod"); final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); + final BigDecimal interestRateDifferential = rs.getBigDecimal("interestRateDifferential"); + final boolean isFloatingInterestRate = rs.getBoolean("isFloatingInterestRate"); final Integer graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment"); final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment"); @@ -873,16 +903,17 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData, - loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, + loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, - interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, - graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, - syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, - fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, - isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement); + isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, expectedFirstRepaymentOnDate, + graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, + loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter, + multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, + isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, + createStandingInstructionAtDisbursement); } } @@ -895,7 +926,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " 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, " + " ls.total_paid_in_advance_derived as totalPaidInAdvanceForPeriod, ls.total_paid_late_derived as totalPaidLateForPeriod, " - + " mc.amount,mc.id as chargeId " + " from m_loan_repayment_schedule ls " + + " mc.amount,mc.id as chargeId " + + " from m_loan_repayment_schedule ls " + " inner join m_loan ml on ml.id = ls.loan_id " + " join m_product_loan_charge plc on plc.product_loan_id = ml.product_id " + " join m_charge mc on mc.id = plc.charge_id "; @@ -1032,8 +1064,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount()); } else if (data.isDueForDisbursement(fromDate, dueDate)) { - if (!excludePastUndisbursed || (excludePastUndisbursed - && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) { + if (!excludePastUndisbursed + || (excludePastUndisbursed && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) { principal = principal.add(data.amount()); if (data.getChargeAmount() == null) { final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( @@ -1093,12 +1125,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { "totalPaidInAdvanceForPeriod"); final BigDecimal totalPaidLateForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalPaidLateForPeriod"); - final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived) - .subtract(penaltyChargesWrittenOff); + final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived).subtract( + penaltyChargesWrittenOff); final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid); - final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue) - .add(penaltyChargesExpectedDue); + final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add( + penaltyChargesExpectedDue); final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod); final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid).add(penaltyChargesPaid); @@ -1139,11 +1171,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { periods.add(periodData); } - return new LoanScheduleData(this.currency, periods, loanTermInDays, totalPrincipalDisbursed, totalPrincipalExpected.getAmount(), - totalPrincipalPaid.getAmount(), totalInterestCharged.getAmount(), totalFeeChargesCharged.getAmount(), - totalPenaltyChargesCharged.getAmount(), totalWaived.getAmount(), totalWrittenOff.getAmount(), - totalRepaymentExpected.getAmount(), totalRepayment.getAmount(), totalPaidInAdvance.getAmount(), - totalPaidLate.getAmount(), totalOutstanding.getAmount()); + return new LoanScheduleData(this.currency, periods, loanTermInDays, totalPrincipalDisbursed, + totalPrincipalExpected.getAmount(), totalPrincipalPaid.getAmount(), totalInterestCharged.getAmount(), + totalFeeChargesCharged.getAmount(), totalPenaltyChargesCharged.getAmount(), totalWaived.getAmount(), + totalWrittenOff.getAmount(), totalRepaymentExpected.getAmount(), totalRepayment.getAmount(), + totalPaidInAdvance.getAmount(), totalPaidLate.getAmount(), totalOutstanding.getAmount()); } } @@ -1156,7 +1188,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " tr.principal_portion_derived as principal, tr.interest_portion_derived as interest, " + " tr.fee_charges_portion_derived as fees, tr.penalty_charges_portion_derived as penalties, " + " tr.overpayment_portion_derived as overpayment, tr.outstanding_loan_balance_derived as outstandingLoanBalance, " - + " tr.unrecognized_income_portion as unrecognizedIncome," + " tr.submitted_on_date as submittedOnDate, " + + " tr.unrecognized_income_portion as unrecognizedIncome," + + " tr.submitted_on_date as submittedOnDate, " + " tr.manually_adjusted_or_reversed as manuallyReversed, " + " pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, " + " pd.receipt_number as receiptNumber, pd.bank_number as bankNumber,pd.routing_code as routingCode, " @@ -1260,7 +1293,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " fromtran.description as fromTransferDescription," + " totran.id as toTransferId, totran.is_reversed as toTransferReversed," + " totran.transaction_date as toTransferDate, totran.amount as toTransferAmount," - + " totran.description as toTransferDescription " + " from m_loan l join m_loan_transaction tr on tr.loan_id = l.id" + + " totran.description as toTransferDescription " + + " from m_loan l join m_loan_transaction tr on tr.loan_id = l.id" + " join m_currency rc on rc.`code` = l.currency_code " + " left join m_account_transfer_transaction fromtran on fromtran.from_loan_transaction_id = tr.id " + " left join m_account_transfer_transaction totran on totran.to_loan_transaction_id = tr.id "; @@ -1320,7 +1354,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { .retrieveInterestRateFrequencyTypeOptions(); final Collection amortizationTypeOptions = this.loanDropdownReadPlatformService .retrieveLoanAmortizationTypeOptions(); - final Collection interestTypeOptions = this.loanDropdownReadPlatformService.retrieveLoanInterestTypeOptions(); + Collection interestTypeOptions = null; + if (loanProduct.isLinkedToFloatingInterestRates()) { + interestTypeOptions = Arrays.asList(interestType(InterestMethod.DECLINING_BALANCE)); + } else { + interestTypeOptions = this.loanDropdownReadPlatformService.retrieveLoanInterestTypeOptions(); + } final Collection interestCalculationPeriodTypeOptions = this.loanDropdownReadPlatformService .retrieveLoanInterestRateCalculatedInPeriodOptions(); final Collection fundOptions = this.fundReadPlatformService.retrieveAllFunds(); @@ -1334,8 +1373,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT }); } else { - chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, - new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT }); + chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, new ChargeTimeType[] { + ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT }); } Integer loanCycleCounter = null; @@ -1395,8 +1434,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { @Override public Collection retrieveCalendars(final Long groupId) { Collection calendarsData = new ArrayList<>(); - calendarsData.addAll( - this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null)); + calendarsData.addAll(this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, + CalendarEntityType.GROUPS.getValue(), null)); calendarsData .addAll(this.calendarReadPlatformService.retrieveCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null)); calendarsData = this.calendarReadPlatformService.updateWithRecurringDates(calendarsData); @@ -1518,13 +1557,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { LoanScheduleAccrualMapper mapper = new LoanScheduleAccrualMapper(); final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("select ").append(mapper.schema()) + sqlBuilder + .append("select ") + .append(mapper.schema()) .append(" where ((ls.fee_charges_amount <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))") .append(" or ( ls.penalty_charges_amount <> if(ls.accrual_penalty_charges_derived is null,0,ls.accrual_penalty_charges_derived))") .append(" or ( ls.interest_amount <> if(ls.accrual_interest_derived is null,0,ls.accrual_interest_derived)))") .append(" and loan.loan_status_id=? and mpl.accounting_type=? and loan.is_npa=0 and ls.duedate <= CURDATE() order by loan.id,ls.duedate"); - return this.jdbcTemplate.query(sqlBuilder.toString(), mapper, - new Object[] { LoanStatus.ACTIVE.getValue(), AccountingRuleType.ACCRUAL_PERIODIC.getValue() }); + return this.jdbcTemplate.query(sqlBuilder.toString(), mapper, new Object[] { LoanStatus.ACTIVE.getValue(), + AccountingRuleType.ACCRUAL_PERIODIC.getValue() }); } @Override @@ -1532,7 +1573,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { LoanSchedulePeriodicAccrualMapper mapper = new LoanSchedulePeriodicAccrualMapper(); final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("select ").append(mapper.schema()) + sqlBuilder + .append("select ") + .append(mapper.schema()) .append(" where ((ls.fee_charges_amount <> if(ls.accrual_fee_charges_derived is null,0, ls.accrual_fee_charges_derived))") .append(" or (ls.penalty_charges_amount <> if(ls.accrual_penalty_charges_derived is null,0,ls.accrual_penalty_charges_derived))") .append(" or (ls.interest_amount <> if(ls.accrual_interest_derived is null,0,ls.accrual_interest_derived)))") @@ -1549,13 +1592,16 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { public String schema() { final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("loan.id as loanId ,if(loan.client_id is null,mg.office_id,mc.office_id) as officeId,") + sqlBuilder + .append("loan.id as loanId ,if(loan.client_id is null,mg.office_id,mc.office_id) as officeId,") .append("loan.accrued_till as accruedTill, loan.repayment_period_frequency_enum as frequencyEnum, ") - .append("loan.interest_calculated_from_date as interestCalculatedFrom, ").append("loan.repay_every as repayEvery,") + .append("loan.interest_calculated_from_date as interestCalculatedFrom, ") + .append("loan.repay_every as repayEvery,") .append("ls.installment as installmentNumber, ") .append("ls.duedate as duedate,ls.fromdate as fromdate ,ls.id as scheduleId,loan.product_id as productId,") .append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,") - .append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ") + .append("ls.penalty_charges_amount as penalty, ") + .append("ls.fee_charges_amount as charges, ") .append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,") .append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,") .append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode") @@ -1610,11 +1656,13 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { public String schema() { final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("loan.id as loanId ,if(loan.client_id is null,mg.office_id,mc.office_id) as officeId,") + sqlBuilder + .append("loan.id as loanId ,if(loan.client_id is null,mg.office_id,mc.office_id) as officeId,") .append("ls.duedate as duedate,ls.fromdate as fromdate,ls.id as scheduleId,loan.product_id as productId,") .append("ls.installment as installmentNumber, ") .append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,") - .append("ls.penalty_charges_amount as penalty, ").append("ls.fee_charges_amount as charges, ") + .append("ls.penalty_charges_amount as penalty, ") + .append("ls.fee_charges_amount as charges, ") .append("ls.accrual_interest_derived as accinterest,ls.accrual_fee_charges_derived as accfeecharege,ls.accrual_penalty_charges_derived as accpenalty,") .append(" loan.currency_code as currencyCode,loan.currency_digits as currencyDigits,loan.currency_multiplesof as inMultiplesOf,") .append("curr.display_symbol as currencyDisplaySymbol,curr.name as currencyName,curr.internationalized_name_code as currencyNameCode") @@ -1687,24 +1735,42 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } @Override - public Collection fetchArrearLoans() { + public Collection fetchLoansForInterestRecalculation() { StringBuilder sqlBuilder = new StringBuilder(); sqlBuilder.append("SELECT ml.id FROM m_loan ml "); sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id "); sqlBuilder.append(" LEFT JOIN m_loan_disbursement_detail dd on dd.loan_id=ml.id and dd.disbursedon_date is null "); + // For Floating rate changes + sqlBuilder + .append(" left join m_product_loan_floating_rates pfr on ml.product_id = pfr.loan_product_id and ml.is_floating_interest_rate = 1"); + sqlBuilder.append(" left join m_floating_rates fr on pfr.floating_rates_id = fr.id"); + sqlBuilder.append(" left join m_floating_rates_periods frp on fr.id = frp.floating_rates_id "); + sqlBuilder.append(" left join m_loan_reschedule_request lrr on lrr.loan_id = ml.id"); + // this is to identify the applicable rates when base rate is changed + sqlBuilder.append(" left join m_floating_rates bfr on bfr.is_base_lending_rate = 1"); + sqlBuilder.append(" left join m_floating_rates_periods bfrp on bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?"); sqlBuilder.append(" WHERE ml.loan_status_id = ? "); - sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 "); sqlBuilder.append(" and ml.is_npa = 0 "); + sqlBuilder.append(" and (("); + sqlBuilder.append("ml.interest_recalculation_enabled = 1 "); sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ?)"); sqlBuilder.append(" and (("); sqlBuilder.append(" mr.completed_derived is false "); sqlBuilder.append(" and mr.duedate < ? )"); - sqlBuilder.append(" or dd.expected_disburse_date < ? ) "); + sqlBuilder.append(" or dd.expected_disburse_date < ? )) "); + sqlBuilder.append(" or ("); + sqlBuilder.append(" fr.is_active = 1 and frp.is_active = 1"); + sqlBuilder.append(" and (frp.created_date >= ? or "); + sqlBuilder.append("(bfrp.id is not null and frp.is_differential_to_base_lending_rate = 1 and frp.from_date >= bfrp.from_date)) "); + sqlBuilder.append("and lrr.loan_id is null"); + sqlBuilder.append(" ))"); sqlBuilder.append(" group by ml.id"); try { String currentdate = formatter.print(DateUtils.getLocalDateOfTenant()); - return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, - new Object[] { LoanStatus.ACTIVE.getValue(), currentdate, currentdate, currentdate }); + // will look only for yesterday modified rates + String yesterday = formatter.print(DateUtils.getLocalDateOfTenant().minusDays(1)); + return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { yesterday, + LoanStatus.ACTIVE.getValue(), currentdate, currentdate, currentdate, yesterday }); } catch (final EmptyResultDataAccessException e) { return null; } @@ -1835,10 +1901,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete, principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, - interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesDue, - feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid, - penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid, - totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding, + interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, + feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, + penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, + totalPaid, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding, totalActualCostOfLoanForPeriod); } } @@ -1847,8 +1913,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { public Date retrieveMinimumDateOfRepaymentTransaction(Long loanId) { // TODO Auto-generated method stub Date date = this.jdbcTemplate.queryForObject( - "select min(transaction_date) from m_loan_transaction where loan_id=? and transaction_type_enum=2", new Object[] { loanId }, - Date.class); + "select min(transaction_date) from m_loan_transaction where loan_id=? and transaction_type_enum=2", + new Object[] { loanId }, Date.class); return date; } @@ -1894,8 +1960,66 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final Collection paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, - retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null, null, - null, null, false); + retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null, + null, null, null, false); + } + + private FloatingRateDTO constructFloatingRateDTO(final Loan loan) { + FloatingRateDTO floatingRateDTO = null; + if (loan.loanProduct().isLinkedToFloatingInterestRate()) { + boolean isFloatingInterestRate = loan.getIsFloatingInterestRate(); + BigDecimal interestRateDiff = loan.getInterestRateDifferential(); + List baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() + .getRatePeriods(); + floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff, + baseLendingRatePeriods); + } + return floatingRateDTO; + } + + @Override + public Collection retrieveLoanInterestRatePeriodData(Long loanId) { + this.context.authenticatedUser(); + + final Loan loan = this.loanRepository.findOne(loanId); + if (loan == null) { throw new LoanNotFoundException(loanId); } + + if (loan.loanProduct().isLinkedToFloatingInterestRate()) { + final Collection intRatePeriodData = new ArrayList<>(); + final Collection intRates = this.floatingRatesReadPlatformService.retrieveInterestRatePeriods(loan + .loanProduct().getFloatingRates().getFloatingRate().getId()); + for (final InterestRatePeriodData rate : intRates) { + if (rate.getFromDate().compareTo(loan.getSubmittedOnDate().toDate()) > 0 && loan.getIsFloatingInterestRate()) { + updateInterestRatePeriodData(rate, loan); + intRatePeriodData.add(rate); + } else if (rate.getFromDate().compareTo(loan.getSubmittedOnDate().toDate()) <= 0) { + updateInterestRatePeriodData(rate, loan); + intRatePeriodData.add(rate); + break; + } + } + + return intRatePeriodData; + } + return null; + } + + private void updateInterestRatePeriodData(InterestRatePeriodData rate, Loan loan) { + rate.setLoanProductDifferentialInterestRate(loan.loanProduct().getFloatingRates().getInterestRateDifferential()); + rate.setLoanDifferentialInterestRate(loan.getInterestRateDifferential()); + + BigDecimal effectiveInterestRate = BigDecimal.ZERO; + effectiveInterestRate = effectiveInterestRate.add(rate.getLoanDifferentialInterestRate()); + effectiveInterestRate = effectiveInterestRate.add(rate.getLoanProductDifferentialInterestRate()); + effectiveInterestRate = effectiveInterestRate.add(rate.getInterestRate()); + if (rate.getBlrInterestRate() != null && rate.isDifferentialToBLR()) { + effectiveInterestRate = effectiveInterestRate.add(rate.getBlrInterestRate()); + } + rate.setEffectiveInterestRate(effectiveInterestRate); + + if (rate.getFromDate().compareTo(loan.getSubmittedOnDate().toDate()) < 0) { + rate.setFromDate(loan.getSubmittedOnDate().toDate()); + } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanSchedularServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanSchedularServiceImpl.java index b135ce159..914fe998a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanSchedularServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanSchedularServiceImpl.java @@ -18,6 +18,7 @@ import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidation import org.mifosplatform.infrastructure.jobs.annotation.CronTarget; import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException; import org.mifosplatform.infrastructure.jobs.service.JobName; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,13 +32,16 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { private final ConfigurationDomainService configurationDomainService; private final LoanReadPlatformService loanReadPlatformService; private final LoanWritePlatformService loanWritePlatformService; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; @Autowired public LoanSchedularServiceImpl(final ConfigurationDomainService configurationDomainService, - final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService) { + final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { this.configurationDomainService = configurationDomainService; this.loanReadPlatformService = loanReadPlatformService; this.loanWritePlatformService = loanWritePlatformService; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; } @Override @@ -92,11 +96,11 @@ public class LoanSchedularServiceImpl implements LoanSchedularService { if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } } } - + @Override @CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN) public void recalculateInterest() { - Collection loanIds = this.loanReadPlatformService.fetchArrearLoans(); + Collection loanIds = this.loanReadPlatformService.fetchLoansForInterestRecalculation(); for (Long loanId : loanIds) { this.loanWritePlatformService.recalculateInterest(loanId); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index f02d18776..458165dc4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -97,6 +97,9 @@ import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUS import org.mifosplatform.portfolio.common.BusinessEventNotificationConstants.BUSINESS_EVENTS; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; @@ -206,6 +209,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final AccountTransferDetailRepository accountTransferDetailRepository; private final BusinessEventNotifierService businessEventNotifierService; private final GuarantorDomainService guarantorDomainService; + private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; @Autowired public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -231,7 +235,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper, final AccountAssociationsRepository accountAssociationRepository, final AccountTransferDetailRepository accountTransferDetailRepository, - final BusinessEventNotifierService businessEventNotifierService, final GuarantorDomainService guarantorDomainService) { + final BusinessEventNotifierService businessEventNotifierService, final GuarantorDomainService guarantorDomainService, + final FloatingRatesReadPlatformService floatingRatesReadPlatformService) { this.context = context; this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanAssembler = loanAssembler; @@ -266,6 +271,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.accountTransferDetailRepository = accountTransferDetailRepository; this.businessEventNotifierService = businessEventNotifierService; this.guarantorDomainService = guarantorDomainService; + this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -354,9 +360,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, - recalculateFrom, overdurPenaltyWaitPeriod); + recalculateFrom, overdurPenaltyWaitPeriod, floatingRateDTO); regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { @@ -513,7 +521,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf for (LoanRepaymentScheduleInstallment installment : installments) { if (installment.getId() == null) { this.repaymentScheduleInstallmentRepository.save(installment); - } + } } this.loanRepository.save(loan); } catch (final DataIntegrityViolationException e) { @@ -608,9 +616,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, firstRepaymentOnDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive( @@ -707,9 +717,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, floatingRateDTO); final Map changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, currentUser); @@ -893,11 +904,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .getTransactionDate(); } + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, @@ -1042,9 +1055,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); @@ -1137,9 +1152,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf recalculateFrom = command.localDateValueOfParameterNamed("transactionDate"); } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); @@ -1221,9 +1238,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); @@ -1357,7 +1376,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf boolean isAppliedOnBackDate = false; LoanCharge loanCharge = null; - LocalDate recalculateFrom =loan.fetchInterestRecalculateFromDate(); + LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate(); if (chargeDefinition.isPercentageOfDisbursementAmount()) { LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null; for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) { @@ -1478,9 +1497,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final Long overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, - recalculateFrom, overdurPenaltyWaitPeriod); + recalculateFrom, overdurPenaltyWaitPeriod, floatingRateDTO); ChangedTransactionDetail changedTransactionDetail = loan.handleRegenerateRepaymentScheduleWithInterestRecalculation( generatorDTO, currentUser); saveLoanWithDataIntegrityViolationChecks(loan); @@ -1521,9 +1542,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, - calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); + calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, + floatingRateDTO); createLoanScheduleArchive(loan, scheduleGeneratorDTO); } @@ -1678,9 +1701,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); Money accruedCharge = Money.zero(loan.getCurrency()); if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { @@ -2440,7 +2465,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf if (loan != null) { boolean reprocessRequired = true; LocalDate recalculatedTill = loan.fetchInterestRecalculateFromDate(); - if(recalculateFrom.isAfter(recalculatedTill)){ + if (recalculateFrom.isAfter(recalculatedTill)) { recalculateFrom = recalculatedTill; } @@ -2590,9 +2615,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf } HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); @@ -2717,10 +2744,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); ChangedTransactionDetail changedTransactionDetail = null; AppUser currentUser = getAppUserIfPresent(); @@ -2857,19 +2886,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf @Transactional @Override public void recalculateInterest(final long loanId) { - AppUser currentUser = getAppUserIfPresent(); Loan loan = this.loanAssembler.assembleFrom(loanId); + LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate(); + AppUser currentUser = getAppUserIfPresent(); this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION, constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); CalendarInstance restCalendarInstance = null; CalendarInstance compoundingCalendarInstance = null; - ApplicationCurrency applicationCurrency = null; - LocalDate calculatedRepaymentsStartingFromDate = null; - List holidays = null; - boolean isHolidayEnabled = false; - WorkingDays workingDays = null; - LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate(); - Long overdurPenaltyWaitPeriod = null; final List existingTransactionIds = new ArrayList<>(); final List existingReversedTransactionIds = new ArrayList<>(); if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { @@ -2877,23 +2900,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); - - final MonetaryCurrency currency = loan.getCurrency(); - applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); - final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), - CalendarEntityType.LOANS.getValue()); - calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( - loan.getDisbursementDate(), loan, calendarInstance); - - isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); - holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); - workingDays = this.workingDaysRepository.findOne(); - overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); } + + final MonetaryCurrency currency = loan.getCurrency(); + ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); + final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), + CalendarEntityType.LOANS.getValue()); + LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate( + loan.getDisbursementDate(), loan, calendarInstance); + + boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + List holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate() + .toDate()); + WorkingDays workingDays = this.workingDaysRepository.findOne(); + Long overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); + FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); + ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod); + overdurPenaltyWaitPeriod, floatingRateDTO); ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO, existingTransactionIds, existingReversedTransactionIds, currentUser); @@ -3022,4 +3048,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf return map; } + private FloatingRateDTO constructFloatingRateDTO(final Loan loan) { + FloatingRateDTO floatingRateDTO = null; + if (loan.loanProduct().isLinkedToFloatingInterestRate()) { + boolean isFloatingInterestRate = loan.getIsFloatingInterestRate(); + BigDecimal interestRateDiff = loan.getInterestRateDifferential(); + List baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() + .getRatePeriods(); + floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff, + baseLendingRatePeriods); + } + return floatingRateDTO; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java index ccc45e65a..73c8e8007 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -44,6 +44,8 @@ import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformServi import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateData; +import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; @@ -73,7 +75,9 @@ public class LoanProductsApiResource { "paymentTypeOptions", "currencyOptions", "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions", "accountingRuleOptions", - "accountingMappingOptions")); + "accountingMappingOptions", "floatingRateOptions", "isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential", + "minDifferentialLendingRate", "defaultDifferentialLendingRate", "maxDifferentialLendingRate", + "isFloatingInterestRateCalculationAllowed")); private final Set PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions")); @@ -95,6 +99,7 @@ public class LoanProductsApiResource { private final ProductMixReadPlatformService productMixReadPlatformService; private final DropdownReadPlatformService commonDropdownReadPlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; + private final FloatingRatesReadPlatformService floatingRateReadPlatformService; @Autowired public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService, @@ -108,7 +113,8 @@ public class LoanProductsApiResource { final DefaultToApiJsonSerializer productMixDataApiJsonSerializer, final ProductMixReadPlatformService productMixReadPlatformService, final DropdownReadPlatformService commonDropdownReadPlatformService, - PaymentTypeReadPlatformService paymentTypeReadPlatformService) { + PaymentTypeReadPlatformService paymentTypeReadPlatformService, + final FloatingRatesReadPlatformService floatingRateReadPlatformService) { this.context = context; this.loanProductReadPlatformService = readPlatformService; this.chargeReadPlatformService = chargeReadPlatformService; @@ -124,6 +130,7 @@ public class LoanProductsApiResource { this.productMixReadPlatformService = productMixReadPlatformService; this.commonDropdownReadPlatformService = commonDropdownReadPlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; + this.floatingRateReadPlatformService = floatingRateReadPlatformService; } @POST @@ -279,13 +286,14 @@ public class LoanProductsApiResource { .retrieveInterestRecalculationFrequencyTypeOptions(); final List preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService .retrivePreCloseInterestCalculationStrategyOptions(); + final List floatingRateOptions = this.floatingRateReadPlatformService.retrieveLookupActive(); return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions, accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions, interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions, - preCloseInterestCalculationStrategyOptions); + preCloseInterestCalculationStrategyOptions, floatingRateOptions); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index dfc44096c..9eed599e0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -25,6 +25,7 @@ import org.mifosplatform.portfolio.common.domain.DaysInMonthType; import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.service.CommonEnumerations; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateData; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.loanaccount.data.LoanInterestRecalculationData; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; @@ -67,6 +68,15 @@ public class LoanProductData { private final BigDecimal maxInterestRatePerPeriod; private final EnumOptionData interestRateFrequencyType; private final BigDecimal annualInterestRate; + + private final boolean isLinkedToFloatingInterestRates; + private final Integer floatingRateId; + private final String floatingRateName; + private final BigDecimal interestRateDifferential; + private final BigDecimal minDifferentialLendingRate; + private final BigDecimal defaultDifferentialLendingRate; + private final BigDecimal maxDifferentialLendingRate; + private final boolean isFloatingInterestRateCalculationAllowed; // settings private final EnumOptionData amortizationType; @@ -135,6 +145,8 @@ public class LoanProductData { @SuppressWarnings("unused") private final List interestRecalculationFrequencyTypeOptions; + @SuppressWarnings("unused") + private final List floatingRateOptions; private final Boolean multiDisburseLoan; private final Integer maxTrancheCount; @@ -165,6 +177,14 @@ public class LoanProductData { final BigDecimal minInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal annualInterestRate = null; + final boolean isLinkedToFloatingInterestRates = false; + final Integer floatingRateId = null; + final String floatingRateName = null; + final BigDecimal interestRateDifferential = null; + final BigDecimal minDifferentialLendingRate = null; + final BigDecimal defaultDifferentialLendingRate = null; + final BigDecimal maxDifferentialLendingRate = null; + final boolean isFloatingInterestRateCalculationAllowed = false; final EnumOptionData repaymentFrequencyType = null; final EnumOptionData interestRateFrequencyType = null; final EnumOptionData amortizationType = null; @@ -217,7 +237,9 @@ public class LoanProductData { graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, - installmentAmountInMultiplesOf, loanProductConfigurableAttributes); + installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, + floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, + maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed); } @@ -236,6 +258,14 @@ public class LoanProductData { final BigDecimal minInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal annualInterestRate = null; + final boolean isLinkedToFloatingInterestRates = false; + final Integer floatingRateId = null; + final String floatingRateName = null; + final BigDecimal interestRateDifferential = null; + final BigDecimal minDifferentialLendingRate = null; + final BigDecimal defaultDifferentialLendingRate = null; + final BigDecimal maxDifferentialLendingRate = null; + final boolean isFloatingInterestRateCalculationAllowed = false; final EnumOptionData repaymentFrequencyType = null; final EnumOptionData interestRateFrequencyType = null; final EnumOptionData amortizationType = null; @@ -290,7 +320,9 @@ public class LoanProductData { graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, - installmentAmountInMultiplesOf, loanProductConfigurableAttributes); + installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, + floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, + maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed); } @@ -313,6 +345,14 @@ public class LoanProductData { final BigDecimal minInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal annualInterestRate = null; + final boolean isLinkedToFloatingInterestRates = false; + final Integer floatingRateId = null; + final String floatingRateName = null; + final BigDecimal interestRateDifferential = null; + final BigDecimal minDifferentialLendingRate = null; + final BigDecimal defaultDifferentialLendingRate = null; + final BigDecimal maxDifferentialLendingRate = null; + final boolean isFloatingInterestRateCalculationAllowed = false; final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(PeriodFrequencyType.MONTHS); final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(PeriodFrequencyType.MONTHS); final EnumOptionData amortizationType = LoanEnumerations.amortizationType(AmortizationMethod.EQUAL_INSTALLMENTS); @@ -371,7 +411,9 @@ public class LoanProductData { daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, - loanProductConfigurableAttributes); + loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, + floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, + maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed); } @@ -406,7 +448,10 @@ public class LoanProductData { final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds, final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstallment, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, boolean canDefineInstallmentAmount, - Integer installmentAmountInMultiplesOf, LoanProductConfigurableAttributes allowAttributeOverrides) { + Integer installmentAmountInMultiplesOf, LoanProductConfigurableAttributes allowAttributeOverrides, + boolean isLinkedToFloatingInterestRates, Integer floatingRateId, String floatingRateName, + BigDecimal interestRateDifferential, BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, + BigDecimal maxDifferentialLendingRate, boolean isFloatingInterestRateCalculationAllowed) { this.id = id; this.name = name; this.shortName = shortName; @@ -427,6 +472,14 @@ public class LoanProductData { this.minInterestRatePerPeriod = minInterestRatePerPeriod; this.maxInterestRatePerPeriod = maxInterestRatePerPeriod; this.annualInterestRate = annualInterestRate; + this.isLinkedToFloatingInterestRates = isLinkedToFloatingInterestRates; + this.floatingRateId = floatingRateId; + this.floatingRateName = floatingRateName; + this.interestRateDifferential = interestRateDifferential; + this.minDifferentialLendingRate = minDifferentialLendingRate; + this.defaultDifferentialLendingRate = defaultDifferentialLendingRate; + this.maxDifferentialLendingRate = maxDifferentialLendingRate; + this.isFloatingInterestRateCalculationAllowed = isFloatingInterestRateCalculationAllowed; this.repaymentFrequencyType = repaymentFrequencyType; this.interestRateFrequencyType = interestRateFrequencyType; this.amortizationType = amortizationType; @@ -457,6 +510,7 @@ public class LoanProductData { this.interestCalculationPeriodTypeOptions = null; this.repaymentFrequencyTypeOptions = null; this.interestRateFrequencyTypeOptions = null; + this.floatingRateOptions = null; this.accountingMappingOptions = null; this.accountingRuleOptions = null; @@ -505,7 +559,7 @@ public class LoanProductData { final List valueConditionTypeOptions, final List daysInMonthTypeOptions, final List daysInYearTypeOptions, final List interestRecalculationCompoundingTypeOptions, final List rescheduleStrategyTypeOptions, final List interestRecalculationFrequencyTypeOptions, - final List preCloseInterestCalculationStrategyOptions) { + final List preCloseInterestCalculationStrategyOptions, final List floatingRateOptions) { this.id = productData.id; this.name = productData.name; this.shortName = productData.shortName; @@ -525,6 +579,14 @@ public class LoanProductData { this.minInterestRatePerPeriod = productData.minInterestRatePerPeriod; this.maxInterestRatePerPeriod = productData.maxInterestRatePerPeriod; this.annualInterestRate = productData.annualInterestRate; + this.isLinkedToFloatingInterestRates = productData.isLinkedToFloatingInterestRates; + this.floatingRateId = productData.floatingRateId; + this.floatingRateName = productData.floatingRateName; + this.interestRateDifferential = productData.interestRateDifferential; + this.minDifferentialLendingRate = productData.minDifferentialLendingRate; + this.defaultDifferentialLendingRate = productData.defaultDifferentialLendingRate; + this.maxDifferentialLendingRate = productData.maxDifferentialLendingRate; + this.isFloatingInterestRateCalculationAllowed = productData.isFloatingInterestRateCalculationAllowed; this.repaymentFrequencyType = productData.repaymentFrequencyType; this.interestRateFrequencyType = productData.interestRateFrequencyType; this.amortizationType = productData.amortizationType; @@ -552,6 +614,7 @@ public class LoanProductData { this.currency = productData.currency; this.fundOptions = fundOptions; this.transactionProcessingStrategyOptions = transactionStrategyOptions; + this.floatingRateOptions = floatingRateOptions; if (this.transactionProcessingStrategyOptions != null && this.transactionProcessingStrategyOptions.size() == 1) { final List listOfOptions = new ArrayList<>(this.transactionProcessingStrategyOptions); @@ -886,4 +949,26 @@ public class LoanProductData { public void setloanProductConfigurableAttributes(LoanProductConfigurableAttributes loanProductConfigurableAttributes) { this.allowAttributeOverrides = loanProductConfigurableAttributes; } + + public boolean isLinkedToFloatingInterestRates() { + return this.isLinkedToFloatingInterestRates; + } + + public BigDecimal getMinDifferentialLendingRate() { + return this.minDifferentialLendingRate; + } + + public BigDecimal getDefaultDifferentialLendingRate() { + return this.defaultDifferentialLendingRate; + } + + public BigDecimal getMaxDifferentialLendingRate() { + return this.maxDifferentialLendingRate; + } + + public boolean isFloatingInterestRateCalculationAllowed() { + return this.isFloatingInterestRateCalculationAllowed; + } + + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 212001966..a381ac099 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanproduct.domain; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -43,6 +44,9 @@ import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.common.domain.DaysInMonthType; import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.domain.FloatingRate; import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; @@ -153,8 +157,15 @@ public class LoanProduct extends AbstractPersistable { @Column(name = "instalment_amount_in_multiples_of", nullable = true) private Integer installmentAmountInMultiplesOf; + @Column(name = "is_linked_to_floating_interest_rates", nullable = false) + private boolean isLinkedToFloatingInterestRate; + + @LazyCollection(LazyCollectionOption.FALSE) + @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true) + private LoanProductFloatingRates floatingRates; + public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, - final List productCharges, final JsonCommand command, final AprCalculator aprCalculator) { + final List productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) { final String name = command.stringValueOfParameterNamed("name"); final String shortName = command.stringValueOfParameterNamed(LoanProductConstants.shortName); @@ -174,12 +185,31 @@ public class LoanProduct extends AbstractPersistable { final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.integerValueOfParameterNamed("amortizationType")); final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command .integerValueOfParameterNamed("repaymentFrequencyType")); - final PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.fromInt(command - .integerValueOfParameterNamed("interestRateFrequencyType")); - final BigDecimal interestRatePerPeriod = command.bigDecimalValueOfParameterNamed("interestRatePerPeriod"); - final BigDecimal minInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("minInterestRatePerPeriod"); - final BigDecimal maxInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod"); - final BigDecimal annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod); + PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.INVALID; + BigDecimal interestRatePerPeriod = null; + BigDecimal minInterestRatePerPeriod = null; + BigDecimal maxInterestRatePerPeriod = null; + BigDecimal annualInterestRate = null; + BigDecimal interestRateDifferential = null; + BigDecimal minDifferentialLendingRate = null; + BigDecimal maxDifferentialLendingRate = null; + BigDecimal defaultDifferentialLendingRate = null; + Boolean isFloatingInterestRateCalculationAllowed = null; + final Boolean isLinkedToFloatingInterestRates = command.booleanObjectValueOfParameterNamed("isLinkedToFloatingInterestRates"); + if (isLinkedToFloatingInterestRates != null && isLinkedToFloatingInterestRates) { + interestRateDifferential = command.bigDecimalValueOfParameterNamed("interestRateDifferential"); + minDifferentialLendingRate = command.bigDecimalValueOfParameterNamed("minDifferentialLendingRate"); + maxDifferentialLendingRate = command.bigDecimalValueOfParameterNamed("maxDifferentialLendingRate"); + defaultDifferentialLendingRate = command.bigDecimalValueOfParameterNamed("defaultDifferentialLendingRate"); + isFloatingInterestRateCalculationAllowed = command + .booleanObjectValueOfParameterNamed("isFloatingInterestRateCalculationAllowed"); + } else { + interestFrequencyType = PeriodFrequencyType.fromInt(command.integerValueOfParameterNamed("interestRateFrequencyType")); + interestRatePerPeriod = command.bigDecimalValueOfParameterNamed("interestRatePerPeriod"); + minInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("minInterestRatePerPeriod"); + maxInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod"); + annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod); + } final Integer repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery"); final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments"); @@ -271,7 +301,10 @@ public class LoanProduct extends AbstractPersistable { outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuarantorFunds, loanProductGuaranteeDetails, principalThresholdForLastInstallment, - accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineEmiAmount, installmentAmountInMultiplesOf, loanConfigurableAttributes); + accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineEmiAmount, installmentAmountInMultiplesOf, + loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate, interestRateDifferential, + minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate, + isFloatingInterestRateCalculationAllowed); } @@ -498,7 +531,10 @@ public class LoanProduct extends AbstractPersistable { final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds, final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final BigDecimal principalThresholdForLastInstallment, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, final boolean canDefineEmiAmount, - final Integer installmentAmountInMultiplesOf, final LoanProductConfigurableAttributes loanProductConfigurableAttributes) { + final Integer installmentAmountInMultiplesOf, final LoanProductConfigurableAttributes loanProductConfigurableAttributes, + Boolean isLinkedToFloatingInterestRates, FloatingRate floatingRate, BigDecimal interestRateDifferential, + BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, + Boolean isFloatingInterestRateCalculationAllowed) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -513,6 +549,12 @@ public class LoanProduct extends AbstractPersistable { this.charges = charges; } + this.isLinkedToFloatingInterestRate = isLinkedToFloatingInterestRates == null ? false : isLinkedToFloatingInterestRates; + if (isLinkedToFloatingInterestRate) { + this.floatingRates = new LoanProductFloatingRates(floatingRate, this, interestRateDifferential, minDifferentialLendingRate, + maxDifferentialLendingRate, defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed); + } + this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, graceOnInterestPayment, @@ -603,9 +645,9 @@ public class LoanProduct extends AbstractPersistable { public Integer getAccountingType() { return this.accountingRule; } - - public List getLoanProductCharges(){ - return this.charges; + + public List getLoanProductCharges() { + return this.charges; } public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) { @@ -616,11 +658,26 @@ public class LoanProduct extends AbstractPersistable { return this.loanConfigurableAttributes; } - public Map update(final JsonCommand command, final AprCalculator aprCalculator) { + public Map update(final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) { final Map actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator); actualChanges.putAll(loanProductMinMaxConstraints().update(command)); + final String isLinkedToFloatingInterestRates = "isLinkedToFloatingInterestRates"; + if (command.isChangeInBooleanParameterNamed(isLinkedToFloatingInterestRates, this.isLinkedToFloatingInterestRate)) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isLinkedToFloatingInterestRates); + actualChanges.put(isLinkedToFloatingInterestRates, newValue); + this.isLinkedToFloatingInterestRate = newValue; + } + + if (this.isLinkedToFloatingInterestRate) { + actualChanges.putAll(loanProductFloatingRates().update(command, floatingRate)); + this.loanProductRelatedDetail.updateForFloatingInterestRates(); + this.loanProductMinMaxConstraints.updateForFloatingInterestRates(); + }else{ + this.floatingRates = null; + } + final String accountingTypeParamName = "accountingRule"; if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, this.accountingRule)) { final Integer newValue = command.integerValueOfParameterNamed(accountingTypeParamName); @@ -907,6 +964,13 @@ public class LoanProduct extends AbstractPersistable { return actualChanges; } + private LoanProductFloatingRates loanProductFloatingRates() { + this.floatingRates = this.floatingRates == null + ? new LoanProductFloatingRates(null, this, null, null, null, null, false) + : this.floatingRates; + return this.floatingRates; + } + public boolean isAccountingDisabled() { return AccountingRuleType.NONE.getValue().equals(this.accountingRule); } @@ -1187,4 +1251,21 @@ public class LoanProduct extends AbstractPersistable { public LoanProductRelatedDetail getLoanProductRelatedDetail() { return loanProductRelatedDetail; } + + public boolean isLinkedToFloatingInterestRate() { + return this.isLinkedToFloatingInterestRate; + } + + public LoanProductFloatingRates getFloatingRates() { + return this.floatingRates; + } + + public Collection fetchInterestRates(final FloatingRateDTO floatingRateDTO) { + Collection applicableRates = new ArrayList<>(1); + if (isLinkedToFloatingInterestRate()) { + applicableRates = getFloatingRates().fetchInterestRates(floatingRateDTO); + } + return applicableRates; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductFloatingRates.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductFloatingRates.java new file mode 100644 index 000000000..11a4b14f1 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductFloatingRates.java @@ -0,0 +1,148 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.loanproduct.domain; + +import java.math.BigDecimal; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO; +import org.mifosplatform.portfolio.floatingrates.data.FloatingRatePeriodData; +import org.mifosplatform.portfolio.floatingrates.domain.FloatingRate; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_product_loan_floating_rates") +public class LoanProductFloatingRates extends AbstractPersistable { + + @OneToOne + @JoinColumn(name = "loan_product_id", nullable = false) + private LoanProduct loanProduct; + + @ManyToOne + @JoinColumn(name = "floating_rates_id", nullable = false) + private FloatingRate floatingRate; + + @Column(name = "interest_rate_differential", nullable = false) + private BigDecimal interestRateDifferential; + + @Column(name = "min_differential_lending_rate", nullable = false) + private BigDecimal minDifferentialLendingRate; + + @Column(name = "default_differential_lending_rate", nullable = false) + private BigDecimal defaultDifferentialLendingRate; + + @Column(name = "max_differential_lending_rate", nullable = false) + private BigDecimal maxDifferentialLendingRate; + + @Column(name = "is_floating_interest_rate_calculation_allowed", nullable = false) + private boolean isFloatingInterestRateCalculationAllowed; + + public LoanProductFloatingRates(){ + + } + public LoanProductFloatingRates(FloatingRate floatingRate, LoanProduct loanProduct, BigDecimal interestRateDifferential, + BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, + boolean isFloatingInterestRateCalculationAllowed){ + this.floatingRate = floatingRate; + this.loanProduct = loanProduct; + this.interestRateDifferential = interestRateDifferential; + this.minDifferentialLendingRate = minDifferentialLendingRate; + this.maxDifferentialLendingRate = maxDifferentialLendingRate; + this.defaultDifferentialLendingRate = defaultDifferentialLendingRate; + this.isFloatingInterestRateCalculationAllowed = isFloatingInterestRateCalculationAllowed; + } + + public LoanProduct getLoanProduct() { + return this.loanProduct; + } + + public FloatingRate getFloatingRate() { + return this.floatingRate; + } + + public BigDecimal getInterestRateDifferential() { + return this.interestRateDifferential; + } + + public BigDecimal getMinDifferentialLendingRate() { + return this.minDifferentialLendingRate; + } + + public BigDecimal getDefaultDifferentialLendingRate() { + return this.defaultDifferentialLendingRate; + } + + public BigDecimal getMaxDifferentialLendingRate() { + return this.maxDifferentialLendingRate; + } + + public boolean isFloatingInterestRateCalculationAllowed() { + return this.isFloatingInterestRateCalculationAllowed; + } + + public Map update(JsonCommand command, FloatingRate floatingRate) { + final Map actualChanges = new LinkedHashMap<>(20); + if (floatingRate != null) { + final String floatingRatesId = "floatingRatesId"; + if (this.floatingRate == null || command.isChangeInLongParameterNamed(floatingRatesId, this.floatingRate.getId())) { + final long newValue = command.longValueOfParameterNamed(floatingRatesId); + actualChanges.put(floatingRatesId, newValue); + this.floatingRate = floatingRate; + } + } + + final String interestRateDifferential = "interestRateDifferential"; + if (command.isChangeInBigDecimalParameterNamed(interestRateDifferential, this.interestRateDifferential)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRateDifferential); + actualChanges.put(interestRateDifferential, newValue); + this.interestRateDifferential = newValue; + } + final String minDifferentialLendingRate = "minDifferentialLendingRate"; + if (command.isChangeInBigDecimalParameterNamed(minDifferentialLendingRate, this.minDifferentialLendingRate)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(minDifferentialLendingRate); + actualChanges.put(minDifferentialLendingRate, newValue); + this.minDifferentialLendingRate = newValue; + } + final String defaultDifferentialLendingRate = "defaultDifferentialLendingRate"; + if (command.isChangeInBigDecimalParameterNamed(defaultDifferentialLendingRate, this.defaultDifferentialLendingRate)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(defaultDifferentialLendingRate); + actualChanges.put(defaultDifferentialLendingRate, newValue); + this.defaultDifferentialLendingRate = newValue; + } + final String maxDifferentialLendingRate = "maxDifferentialLendingRate"; + if (command.isChangeInBigDecimalParameterNamed(maxDifferentialLendingRate, this.maxDifferentialLendingRate)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(maxDifferentialLendingRate); + actualChanges.put(maxDifferentialLendingRate, newValue); + this.maxDifferentialLendingRate = newValue; + } + final String isFloatingInterestRateCalculationAllowed = "isFloatingInterestRateCalculationAllowed"; + if (command + .isChangeInBooleanParameterNamed(isFloatingInterestRateCalculationAllowed, this.isFloatingInterestRateCalculationAllowed)) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isFloatingInterestRateCalculationAllowed); + actualChanges.put(isFloatingInterestRateCalculationAllowed, newValue); + this.isFloatingInterestRateCalculationAllowed = newValue; + } + + return actualChanges; + } + + public Collection fetchInterestRates(final FloatingRateDTO floatingRateDTO) { + floatingRateDTO.addInterestRateDiff(this.interestRateDifferential); + return floatingRate.fetchInterestRates(floatingRateDTO); + + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java index fe089dee6..f73bdb789 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java @@ -147,4 +147,9 @@ public class LoanProductMinMaxConstraints { return this.maxNumberOfRepayments; } + public void updateForFloatingInterestRates() { + this.minNominalInterestRatePerPeriod = null; + this.maxNominalInterestRatePerPeriod = null; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 3bb708784..ee3914f81 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -43,16 +43,16 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "principal_amount", scale = 6, precision = 19, nullable = true) private BigDecimal principal; - @Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = false) + @Column(name = "nominal_interest_rate_per_period", scale = 6, precision = 19, nullable = true) private BigDecimal nominalInterestRatePerPeriod; // FIXME - move away form JPA ordinal use for enums using just integer - // requires sql patch for existing users of software. @Enumerated(EnumType.ORDINAL) - @Column(name = "interest_period_frequency_enum", nullable = false) + @Column(name = "interest_period_frequency_enum", nullable = true) private PeriodFrequencyType interestPeriodFrequencyType; - @Column(name = "annual_nominal_interest_rate", scale = 6, precision = 19, nullable = false) + @Column(name = "annual_nominal_interest_rate", scale = 6, precision = 19, nullable = true) private BigDecimal annualNominalInterestRate; // FIXME - move away form JPA ordinal use for enums using just integer - @@ -205,17 +205,20 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Override public BigDecimal getNominalInterestRatePerPeriod() { - return BigDecimal.valueOf(Double.valueOf(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString())); + return this.nominalInterestRatePerPeriod == null? null + : BigDecimal.valueOf(Double.valueOf(this.nominalInterestRatePerPeriod.stripTrailingZeros().toString())); } @Override public PeriodFrequencyType getInterestPeriodFrequencyType() { - return this.interestPeriodFrequencyType; + return this.interestPeriodFrequencyType == null? PeriodFrequencyType.INVALID + : this.interestPeriodFrequencyType; } @Override public BigDecimal getAnnualNominalInterestRate() { - return BigDecimal.valueOf(Double.valueOf(this.annualNominalInterestRate.stripTrailingZeros().toString())); + return this.annualNominalInterestRate == null? null + :BigDecimal.valueOf(Double.valueOf(this.annualNominalInterestRate.stripTrailingZeros().toString())); } @Override @@ -365,7 +368,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche } final String interestRateFrequencyTypeParamName = "interestRateFrequencyType"; - if (command.isChangeInIntegerParameterNamed(interestRateFrequencyTypeParamName, this.interestPeriodFrequencyType.getValue())) { + final int interestPeriodFrequencyType = this.interestPeriodFrequencyType == null? + PeriodFrequencyType.INVALID.getValue() : this.interestPeriodFrequencyType.getValue(); + if (command.isChangeInIntegerParameterNamed(interestRateFrequencyTypeParamName, interestPeriodFrequencyType)) { final Integer newValue = command.integerValueOfParameterNamed(interestRateFrequencyTypeParamName); actualChanges.put(interestRateFrequencyTypeParamName, newValue); actualChanges.put("locale", localeAsInput); @@ -567,4 +572,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche return this.inArrearsTolerance; } + public void updateForFloatingInterestRates() { + this.nominalInterestRatePerPeriod = null; + this.interestPeriodFrequencyType = PeriodFrequencyType.INVALID; + this.annualNominalInterestRate = null; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/exception/LoanProductCannotBeModifiedDueToNonClosedLoansException.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/exception/LoanProductCannotBeModifiedDueToNonClosedLoansException.java new file mode 100644 index 000000000..fa2326a85 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/exception/LoanProductCannotBeModifiedDueToNonClosedLoansException.java @@ -0,0 +1,15 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.loanproduct.exception; + +import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class LoanProductCannotBeModifiedDueToNonClosedLoansException extends AbstractPlatformDomainRuleException { + public LoanProductCannotBeModifiedDueToNonClosedLoansException(final Long id) { + super("error.msg.loanproduct.not.modifiable.due.to.non.closed.loans", + "Loan product with identifier " + id + " cannot be modified due to non closed loans associated with it.", id); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 2f9fb449c..62a23adb4 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -54,7 +54,9 @@ public final class LoanProductDataValidator { "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "graceOnPrincipalPayment", "graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule", "includeInBorrowerCycle", "startDate", - "closeDate", "externalId", LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), + "closeDate", "externalId", "isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential", + "minDifferentialLendingRate", "defaultDifferentialLendingRate", "maxDifferentialLendingRate", + "isFloatingInterestRateCalculationAllowed", LOAN_PRODUCT_ACCOUNTING_PARAMS.FEES_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.FUND_SOURCE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_FEES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INCOME_FROM_PENALTIES.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_ON_LOANS.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(), @@ -214,48 +216,6 @@ public final class LoanProductDataValidator { Locale.getDefault()); baseDataValidator.reset().parameter("repaymentFrequencyType").value(repaymentFrequencyType).notNull().inMinMaxRange(0, 3); - final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); - baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount(); - - final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod"; - BigDecimal minInterestRatePerPeriod = null; - if (this.fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) { - minInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName, - element); - baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull() - .zeroOrPositiveAmount(); - } - - final String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod"; - BigDecimal maxInterestRatePerPeriod = null; - if (this.fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) { - maxInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName, - element); - baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull() - .zeroOrPositiveAmount(); - } - - if (maxInterestRatePerPeriod != null && maxInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) { - if (minInterestRatePerPeriod != null && minInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) { - baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod) - .notLessThanMin(minInterestRatePerPeriod); - if (minInterestRatePerPeriod.compareTo(maxInterestRatePerPeriod) <= 0) { - baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod) - .inMinAndMaxAmountRange(minInterestRatePerPeriod, maxInterestRatePerPeriod); - } - } else { - baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod) - .notGreaterThanMax(maxInterestRatePerPeriod); - } - } else if (minInterestRatePerPeriod != null && minInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) { - baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod) - .notLessThanMin(minInterestRatePerPeriod); - } - - final Integer interestRateFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("interestRateFrequencyType", element, - Locale.getDefault()); - baseDataValidator.reset().parameter("interestRateFrequencyType").value(interestRateFrequencyType).notNull().inMinMaxRange(0, 3); - // settings final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault()); baseDataValidator.reset().parameter("amortizationType").value(amortizationType).notNull().inMinMaxRange(0, 1); @@ -330,6 +290,168 @@ public final class LoanProductDataValidator { } } + //interest rates + if(this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element) + && this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true){ + if(this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)){ + baseDataValidator.reset().parameter("interestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true", + "interestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true"); + } + + if(this.fromApiJsonHelper.parameterExists("minInterestRatePerPeriod", element)){ + baseDataValidator.reset().parameter("minInterestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true", + "minInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true"); + } + + if(this.fromApiJsonHelper.parameterExists("maxInterestRatePerPeriod", element)){ + baseDataValidator.reset().parameter("maxInterestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true", + "maxInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true"); + } + + if(this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)){ + baseDataValidator.reset().parameter("interestRateFrequencyType").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true", + "interestRateFrequencyType param is not supported when isLinkedToFloatingInterestRates is true"); + } + if((interestType == null || interestType != InterestMethod.DECLINING_BALANCE.getValue()) + || (isInterestRecalculationEnabled == null || isInterestRecalculationEnabled == false)){ + baseDataValidator.reset().parameter("isLinkedToFloatingInterestRates").failWithCode("supported.only.for.declining.balance.interest.recalculation.enabled", + "Floating interest rates are supported only for declining balance and interest recalculation enabled loan products"); + } + + final Integer floatingRatesId = this.fromApiJsonHelper.extractIntegerNamed("floatingRatesId", element, + Locale.getDefault()); + baseDataValidator.reset().parameter("floatingRatesId").value(floatingRatesId).notNull(); + + final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRateDifferential", element); + baseDataValidator.reset().parameter("interestRateDifferential").value(interestRateDifferential).notNull().zeroOrPositiveAmount(); + + final String minDifferentialLendingRateParameterName = "minDifferentialLendingRate"; + BigDecimal minDifferentialLendingRate = null; + if (this.fromApiJsonHelper.parameterExists(minDifferentialLendingRateParameterName, element)) { + minDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minDifferentialLendingRateParameterName, + element); + baseDataValidator.reset().parameter(minDifferentialLendingRateParameterName).value(minDifferentialLendingRate).ignoreIfNull() + .zeroOrPositiveAmount(); + } + + final String defaultDifferentialLendingRateParameterName = "defaultDifferentialLendingRate"; + BigDecimal defaultDifferentialLendingRate = null; + if (this.fromApiJsonHelper.parameterExists(defaultDifferentialLendingRateParameterName, element)) { + defaultDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(defaultDifferentialLendingRateParameterName, + element); + baseDataValidator.reset().parameter(defaultDifferentialLendingRateParameterName).value(defaultDifferentialLendingRate).ignoreIfNull() + .zeroOrPositiveAmount(); + } + + final String maxDifferentialLendingRateParameterName = "maxDifferentialLendingRate"; + BigDecimal maxDifferentialLendingRate = null; + if (this.fromApiJsonHelper.parameterExists(maxDifferentialLendingRateParameterName, element)) { + maxDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxDifferentialLendingRateParameterName, + element); + baseDataValidator.reset().parameter(maxDifferentialLendingRateParameterName).value(maxDifferentialLendingRate).ignoreIfNull() + .zeroOrPositiveAmount(); + } + + if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter("defaultDifferentialLendingRate").value(defaultDifferentialLendingRate) + .notLessThanMin(minDifferentialLendingRate); + } + } + + if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate) + .notLessThanMin(minDifferentialLendingRate); + } + } + + if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate) + .notLessThanMin(defaultDifferentialLendingRate); + } + } + + final Boolean isFloatingInterestRateCalculationAllowed = this.fromApiJsonHelper.extractBooleanNamed( + "isFloatingInterestRateCalculationAllowed", element); + baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed") + .value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false); + } else { + if(this.fromApiJsonHelper.parameterExists("floatingRatesId", element)){ + baseDataValidator.reset().parameter("floatingRatesId").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "floatingRatesId param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)){ + baseDataValidator.reset().parameter("interestRateDifferential").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "interestRateDifferential param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("minDifferentialLendingRate", element)){ + baseDataValidator.reset().parameter("minDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "minDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("defaultDifferentialLendingRate", element)){ + baseDataValidator.reset().parameter("defaultDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "defaultDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("maxDifferentialLendingRate", element)){ + baseDataValidator.reset().parameter("maxDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "maxDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)){ + baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount(); + + final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod"; + BigDecimal minInterestRatePerPeriod = null; + if (this.fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) { + minInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName, + element); + baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull() + .zeroOrPositiveAmount(); + } + + final String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod"; + BigDecimal maxInterestRatePerPeriod = null; + if (this.fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) { + maxInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName, + element); + baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull() + .zeroOrPositiveAmount(); + } + + if (maxInterestRatePerPeriod != null && maxInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) { + if (minInterestRatePerPeriod != null && minInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod) + .notLessThanMin(minInterestRatePerPeriod); + if (minInterestRatePerPeriod.compareTo(maxInterestRatePerPeriod) <= 0) { + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod) + .inMinAndMaxAmountRange(minInterestRatePerPeriod, maxInterestRatePerPeriod); + } + } else { + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod) + .notGreaterThanMax(maxInterestRatePerPeriod); + } + } else if (minInterestRatePerPeriod != null && minInterestRatePerPeriod.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod) + .notLessThanMin(minInterestRatePerPeriod); + } + + final Integer interestRateFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("interestRateFrequencyType", element, + Locale.getDefault()); + baseDataValidator.reset().parameter("interestRateFrequencyType").value(interestRateFrequencyType).notNull().inMinMaxRange(0, 3); + } + + // Guarantee Funds Boolean holdGuaranteeFunds = false; if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) { @@ -746,30 +868,6 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter("inArrearsTolerance").value(inArrearsTolerance).ignoreIfNull().zeroOrPositiveAmount(); } - final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod"; - BigDecimal minInterestRatePerPeriod = null; - if (this.fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) { - minInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName, - element); - baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull() - .zeroOrPositiveAmount(); - } - - final String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod"; - BigDecimal maxInterestRatePerPeriod = null; - if (this.fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) { - maxInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName, - element); - baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull() - .zeroOrPositiveAmount(); - } - - if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) { - final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", - element); - baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount(); - } - final String minNumberOfRepaymentsParameterName = "minNumberOfRepayments"; Integer minNumberOfRepayments = null; if (this.fromApiJsonHelper.parameterExists(minNumberOfRepaymentsParameterName, element)) { @@ -841,12 +939,6 @@ public final class LoanProductDataValidator { } // - if (this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)) { - final Integer interestRateFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("interestRateFrequencyType", element, - Locale.getDefault()); - baseDataValidator.reset().parameter("interestRateFrequencyType").value(interestRateFrequencyType).notNull().inMinMaxRange(0, 3); - } - if (this.fromApiJsonHelper.parameterExists("amortizationType", element)) { final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault()); baseDataValidator.reset().parameter("amortizationType").value(amortizationType).notNull().inMinMaxRange(0, 1); @@ -906,6 +998,176 @@ public final class LoanProductDataValidator { } } + //interest rates + boolean isLinkedToFloatingInterestRates = loanProduct.isLinkedToFloatingInterestRate(); + if(this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)){ + isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element); + } + if(isLinkedToFloatingInterestRates){ + if(this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)){ + baseDataValidator.reset().parameter("interestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true", + "interestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true"); + } + + if(this.fromApiJsonHelper.parameterExists("minInterestRatePerPeriod", element)){ + baseDataValidator.reset().parameter("minInterestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true", + "minInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true"); + } + + if(this.fromApiJsonHelper.parameterExists("maxInterestRatePerPeriod", element)){ + baseDataValidator.reset().parameter("maxInterestRatePerPeriod").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true", + "maxInterestRatePerPeriod param is not supported when isLinkedToFloatingInterestRates is true"); + } + + if(this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)){ + baseDataValidator.reset().parameter("interestRateFrequencyType").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.true", + "interestRateFrequencyType param is not supported when isLinkedToFloatingInterestRates is true"); + } + + Integer interestType = this.fromApiJsonHelper.parameterExists("interestType", element) + ? this.fromApiJsonHelper.extractIntegerNamed("interestType", element, Locale.getDefault()) + : loanProduct.getLoanProductRelatedDetail().getInterestMethod().getValue(); + if((interestType == null || interestType != InterestMethod.DECLINING_BALANCE.getValue()) + || (isInterestRecalculationEnabled == null || isInterestRecalculationEnabled == false)){ + baseDataValidator.reset().parameter("isLinkedToFloatingInterestRates").failWithCode("supported.only.for.declining.balance.interest.recalculation.enabled", + "Floating interest rates are supported only for declining balance and interest recalculation enabled loan products"); + } + + Long floatingRatesId = loanProduct.getFloatingRates() == null? null: loanProduct.getFloatingRates().getFloatingRate().getId(); + if(this.fromApiJsonHelper.parameterExists("floatingRatesId", element)){ + floatingRatesId = this.fromApiJsonHelper.extractLongNamed("floatingRatesId", element); + } + baseDataValidator.reset().parameter("floatingRatesId").value(floatingRatesId).notNull(); + + BigDecimal interestRateDifferential = loanProduct.getFloatingRates() == null? null: loanProduct.getFloatingRates().getInterestRateDifferential(); + if(this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)){ + interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRateDifferential", element); + } + baseDataValidator.reset().parameter("interestRateDifferential").value(interestRateDifferential).notNull().zeroOrPositiveAmount(); + + final String minDifferentialLendingRateParameterName = "minDifferentialLendingRate"; + BigDecimal minDifferentialLendingRate = loanProduct.getFloatingRates() == null + ? null:loanProduct.getFloatingRates().getMinDifferentialLendingRate(); + if (this.fromApiJsonHelper.parameterExists(minDifferentialLendingRateParameterName, element)) { + minDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minDifferentialLendingRateParameterName, + element); + } + baseDataValidator.reset().parameter(minDifferentialLendingRateParameterName).value(minDifferentialLendingRate).ignoreIfNull() + .zeroOrPositiveAmount(); + + final String defaultDifferentialLendingRateParameterName = "defaultDifferentialLendingRate"; + BigDecimal defaultDifferentialLendingRate = loanProduct.getFloatingRates() == null + ? null:loanProduct.getFloatingRates().getDefaultDifferentialLendingRate(); + if (this.fromApiJsonHelper.parameterExists(defaultDifferentialLendingRateParameterName, element)) { + defaultDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(defaultDifferentialLendingRateParameterName, + element); + } + baseDataValidator.reset().parameter(defaultDifferentialLendingRateParameterName).value(defaultDifferentialLendingRate).ignoreIfNull() + .zeroOrPositiveAmount(); + + final String maxDifferentialLendingRateParameterName = "maxDifferentialLendingRate"; + BigDecimal maxDifferentialLendingRate = loanProduct.getFloatingRates() == null + ? null:loanProduct.getFloatingRates().getMaxDifferentialLendingRate(); + if (this.fromApiJsonHelper.parameterExists(maxDifferentialLendingRateParameterName, element)) { + maxDifferentialLendingRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxDifferentialLendingRateParameterName, + element); + } + baseDataValidator.reset().parameter(maxDifferentialLendingRateParameterName).value(maxDifferentialLendingRate).ignoreIfNull() + .zeroOrPositiveAmount(); + + if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter("defaultDifferentialLendingRate").value(defaultDifferentialLendingRate) + .notLessThanMin(minDifferentialLendingRate); + } + } + + if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + if (minDifferentialLendingRate != null && minDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate) + .notLessThanMin(minDifferentialLendingRate); + } + } + + if (maxDifferentialLendingRate != null && maxDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + if (defaultDifferentialLendingRate != null && defaultDifferentialLendingRate.compareTo(BigDecimal.ZERO) != -1) { + baseDataValidator.reset().parameter("maxDifferentialLendingRate").value(maxDifferentialLendingRate) + .notLessThanMin(defaultDifferentialLendingRate); + } + } + + Boolean isFloatingInterestRateCalculationAllowed = loanProduct.getFloatingRates() == null ? null + : loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed(); + if(this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)){ + isFloatingInterestRateCalculationAllowed = this.fromApiJsonHelper.extractBooleanNamed( + "isFloatingInterestRateCalculationAllowed", element); + } + baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed") + .value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false); + } else { + if(this.fromApiJsonHelper.parameterExists("floatingRatesId", element)){ + baseDataValidator.reset().parameter("floatingRatesId").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "floatingRatesId param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("interestRateDifferential", element)){ + baseDataValidator.reset().parameter("interestRateDifferential").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "interestRateDifferential param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("minDifferentialLendingRate", element)){ + baseDataValidator.reset().parameter("minDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "minDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("defaultDifferentialLendingRate", element)){ + baseDataValidator.reset().parameter("defaultDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "defaultDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("maxDifferentialLendingRate", element)){ + baseDataValidator.reset().parameter("maxDifferentialLendingRate").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "maxDifferentialLendingRate param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + if(this.fromApiJsonHelper.parameterExists("isFloatingInterestRateCalculationAllowed", element)){ + baseDataValidator.reset().parameter("isFloatingInterestRateCalculationAllowed").failWithCode("not.supported.when.isLinkedToFloatingInterestRates.is.false", + "isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false"); + } + + final String minInterestRatePerPeriodParameterName = "minInterestRatePerPeriod"; + BigDecimal minInterestRatePerPeriod = loanProduct.getMinNominalInterestRatePerPeriod(); + if (this.fromApiJsonHelper.parameterExists(minInterestRatePerPeriodParameterName, element)) { + minInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minInterestRatePerPeriodParameterName, + element); + } + baseDataValidator.reset().parameter(minInterestRatePerPeriodParameterName).value(minInterestRatePerPeriod).ignoreIfNull() + .zeroOrPositiveAmount(); + + final String maxInterestRatePerPeriodParameterName = "maxInterestRatePerPeriod"; + BigDecimal maxInterestRatePerPeriod = loanProduct.getMaxNominalInterestRatePerPeriod(); + if (this.fromApiJsonHelper.parameterExists(maxInterestRatePerPeriodParameterName, element)) { + maxInterestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxInterestRatePerPeriodParameterName, + element); + } + baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull() + .zeroOrPositiveAmount(); + + BigDecimal interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod(); + if (this.fromApiJsonHelper.parameterExists("interestRatePerPeriod", element)) { + interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", + element); + } + baseDataValidator.reset().parameter("interestRatePerPeriod").value(interestRatePerPeriod).notNull().zeroOrPositiveAmount(); + + Integer interestRateFrequencyType = loanProduct.getLoanProductRelatedDetail().getInterestPeriodFrequencyType().getValue(); + if (this.fromApiJsonHelper.parameterExists("interestRateFrequencyType", element)) { + interestRateFrequencyType = this.fromApiJsonHelper.extractIntegerNamed("interestRateFrequencyType", element, + Locale.getDefault()); + } + baseDataValidator.reset().parameter("interestRateFrequencyType").value(interestRateFrequencyType).notNull().inMinMaxRange(0, 3); + } + // Guarantee Funds Boolean holdGuaranteeFunds = loanProduct.isHoldGuaranteeFundsEnabled(); if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) { @@ -1303,6 +1565,11 @@ public final class LoanProductDataValidator { private void validateNominalInterestRatePerPeriodMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct, final DataValidatorBuilder baseDataValidator) { + if((this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element) + && this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true) + || loanProduct.isLinkedToFloatingInterestRate()){ + return; + } boolean iRPUpdated = false; boolean minIRPUpdated = false; boolean maxIRPUpdated = false; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index f580154ff..797ef8fc3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -199,7 +199,7 @@ public class LoanEnumerations { return optionData; } - public static EnumOptionData interestRateFrequencyType(final int id) { + public static EnumOptionData interestRateFrequencyType(final Integer id) { return interestRateFrequencyType(PeriodFrequencyType.fromInt(id)); } @@ -503,6 +503,10 @@ public class LoanEnumerations { optionData = new EnumOptionData(LoanTermVariationType.EMI_AMOUNT.getValue().longValue(), LoanTermVariationType.EMI_AMOUNT.getCode(), "emiAmount"); break; + case INTEREST_RATE: + optionData = new EnumOptionData(LoanTermVariationType.INTEREST_RATE.getValue().longValue(), + LoanTermVariationType.INTEREST_RATE.getCode(), "interestRate"); + break; default: optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(), LoanTermVariationType.INVALID.getCode(), "Invalid"); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 9042868de..8fcf3b1d6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -192,13 +192,23 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId, " + "lca.id as lcaId, lca.amortization_method_enum as amortizationBoolean, lca.interest_method_enum as interestMethodConfigBoolean, " + "lca.loan_transaction_strategy_id as transactionProcessingStrategyBoolean,lca.interest_calculated_in_period_enum as interestCalcPeriodBoolean, lca.arrearstolerance_amount as arrearsToleranceBoolean, " - + "lca.repay_every as repaymentFrequencyBoolean, lca.moratorium as graceOnPrincipalAndInterestBoolean, lca.grace_on_arrears_ageing as graceOnArrearsAgingBoolean " + + "lca.repay_every as repaymentFrequencyBoolean, lca.moratorium as graceOnPrincipalAndInterestBoolean, lca.grace_on_arrears_ageing as graceOnArrearsAgingBoolean, " + + "lp.is_linked_to_floating_interest_rates as isLinkedToFloatingInterestRates, " + + "lfr.floating_rates_id as floatingRateId, " + + "fr.name as floatingRateName, " + + "lfr.interest_rate_differential as interestRateDifferential, " + + "lfr.min_differential_lending_rate as minDifferentialLendingRate, " + + "lfr.default_differential_lending_rate as defaultDifferentialLendingRate, " + + "lfr.max_differential_lending_rate as maxDifferentialLendingRate, " + + "lfr.is_floating_interest_rate_calculation_allowed as isFloatingInterestRateCalculationAllowed " + " from m_product_loan lp " + " left join m_fund f on f.id = lp.fund_id " + " left join m_product_loan_recalculation_details lpr on lpr.product_id=lp.id " + " left join m_product_loan_guarantee_details lpg on lpg.loan_product_id=lp.id " + " left join ref_loan_transaction_processing_strategy ltps on ltps.id = lp.loan_transaction_strategy_id" + " left join m_product_loan_configurable_attributes lca on lca.loan_product_id = lp.id " + + " left join m_product_loan_floating_rates as lfr on lfr.loan_product_id = lp.id " + + " left join m_floating_rates as fr on lfr.floating_rates_id = fr.id " + " join m_currency curr on curr.code = lp.currency_code"; } @@ -250,13 +260,22 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final BigDecimal maxInterestRatePerPeriod = rs.getBigDecimal("maxInterestRatePerPeriod"); final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); + final boolean isLinkedToFloatingInterestRates = rs.getBoolean("isLinkedToFloatingInterestRates"); + final Integer floatingRateId = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "floatingRateId"); + final String floatingRateName = rs.getString("floatingRateName"); + final BigDecimal interestRateDifferential = rs.getBigDecimal("interestRateDifferential"); + final BigDecimal minDifferentialLendingRate = rs.getBigDecimal("minDifferentialLendingRate"); + final BigDecimal defaultDifferentialLendingRate = rs.getBigDecimal("defaultDifferentialLendingRate"); + final BigDecimal maxDifferentialLendingRate = rs.getBigDecimal("maxDifferentialLendingRate"); + final boolean isFloatingInterestRateCalculationAllowed = rs.getBoolean("isFloatingInterestRateCalculationAllowed"); + final int repaymentFrequencyTypeId = JdbcSupport.getInteger(rs, "repaymentPeriodFrequency"); final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeId); final int amortizationTypeId = JdbcSupport.getInteger(rs, "amortizationMethod"); final EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeId); - final int interestRateFrequencyTypeId = JdbcSupport.getInteger(rs, "interestRatePerPeriodFreq"); + final Integer interestRateFrequencyTypeId = JdbcSupport.getInteger(rs, "interestRatePerPeriodFreq"); final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(interestRateFrequencyTypeId); final int interestTypeId = JdbcSupport.getInteger(rs, "interestMethod"); @@ -377,7 +396,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData, principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, - installmentAmountInMultiplesOf, allowAttributeOverrides); + installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId, + floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, + maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index 530894a9c..5e8a81d65 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -21,9 +21,12 @@ import org.mifosplatform.infrastructure.entityaccess.service.MifosEntityAccessUt import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; +import org.mifosplatform.portfolio.floatingrates.domain.FloatingRate; +import org.mifosplatform.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper; import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.fund.domain.FundRepository; import org.mifosplatform.portfolio.fund.exception.FundNotFoundException; +import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; @@ -31,6 +34,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; +import org.mifosplatform.portfolio.loanproduct.exception.LoanProductCannotBeModifiedDueToNonClosedLoansException; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductDateException; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductDataValidator; @@ -57,6 +61,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro private final ChargeRepositoryWrapper chargeRepository; private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService; private final MifosEntityAccessUtil mifosEntityAccessUtil; + private final FloatingRateRepositoryWrapper floatingRateRepository; + private final LoanRepository loanRepository; @Autowired public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -65,7 +71,9 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository, final ChargeRepositoryWrapper chargeRepository, final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService, - final MifosEntityAccessUtil mifosEntityAccessUtil) { + final MifosEntityAccessUtil mifosEntityAccessUtil, + final FloatingRateRepositoryWrapper floatingRateRepository, + final LoanRepository loanRepository) { this.context = context; this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.loanProductRepository = loanProductRepository; @@ -75,6 +83,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro this.chargeRepository = chargeRepository; this.accountMappingWritePlatformService = accountMappingWritePlatformService; this.mifosEntityAccessUtil = mifosEntityAccessUtil; + this.floatingRateRepository = floatingRateRepository; + this.loanRepository = loanRepository; } @Transactional @@ -96,8 +106,13 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro final String currencyCode = command.stringValueOfParameterNamed("currencyCode"); final List charges = assembleListOfProductCharges(command, currencyCode); + FloatingRate floatingRate = null; + if(command.parameterExists("floatingRatesId")){ + floatingRate = this.floatingRateRepository + .findOneWithNotFoundDetection(command.longValueOfParameterNamed("floatingRatesId")); + } final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command, - this.aprCalculator); + this.aprCalculator, floatingRate); loanproduct.updateLoanProductInRelatedClasses(); this.loanProductRepository.save(loanproduct); @@ -154,7 +169,18 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro this.fromApiJsonDeserializer.validateForUpdate(command.json(), product); validateInputDates(command); - final Map changes = product.update(command, this.aprCalculator); + if(anyChangeInCriticalFloatingRateLinkedParams(command, product) + && this.loanRepository.doNonClosedLoanAccountsExistForProduct(product.getId())){ + throw new LoanProductCannotBeModifiedDueToNonClosedLoansException(product.getId()); + } + + FloatingRate floatingRate = null; + if(command.parameterExists("floatingRatesId")){ + floatingRate = this.floatingRateRepository + .findOneWithNotFoundDetection(command.longValueOfParameterNamed("floatingRatesId")); + } + + final Map changes = product.update(command, this.aprCalculator, floatingRate); if (changes.containsKey("fundId")) { final Long fundId = (Long) changes.get("fundId"); @@ -199,7 +225,15 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro } - private List assembleListOfProductCharges(final JsonCommand command, final String currencyCode) { + private boolean anyChangeInCriticalFloatingRateLinkedParams(JsonCommand command, LoanProduct product) { + final boolean isChangeFromFloatingToFlatOrViceVersa = command.isChangeInBooleanParameterNamed("isLinkedToFloatingInterestRates", product.isLinkedToFloatingInterestRate()); + final boolean isChangeInCriticalFloatingRateParams = product.getFloatingRates() != null + && (command.isChangeInLongParameterNamed("floatingRatesId", product.getFloatingRates().getFloatingRate().getId()) + || command.isChangeInBigDecimalParameterNamed("interestRateDifferential", product.getFloatingRates().getInterestRateDifferential())); + return isChangeFromFloatingToFlatOrViceVersa || isChangeInCriticalFloatingRateParams; + } + + private List assembleListOfProductCharges(final JsonCommand command, final String currencyCode) { final List charges = new ArrayList<>(); diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V279__floating_rates.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V279__floating_rates.sql new file mode 100644 index 000000000..f7d04b884 --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V279__floating_rates.sql @@ -0,0 +1,61 @@ +CREATE TABLE `m_floating_rates` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `name` VARCHAR(200) NOT NULL, + `is_base_lending_rate` BIT(1) NOT NULL DEFAULT 0, + `is_active` BIT(1) NOT NULL DEFAULT 1, + `createdby_id` BIGINT(20) NOT NULL, + `created_date` DATETIME NOT NULL, + `lastmodifiedby_id` BIGINT(20) NOT NULL, + `lastmodified_date` DATETIME NOT NULL, + PRIMARY KEY (`id`), + UNIQUE INDEX `unq_name` (`name`) +); + +CREATE TABLE `m_floating_rates_periods` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `floating_rates_id` BIGINT(20) NOT NULL, + `from_date` DATETIME NOT NULL, + `interest_rate` DECIMAL(19,6) NOT NULL, + `is_differential_to_base_lending_rate` BIT(1) NOT NULL DEFAULT 0, + `is_active` BIT(1) NOT NULL DEFAULT 1, + `createdby_id` BIGINT(20) NOT NULL, + `created_date` DATETIME NOT NULL, + `lastmodifiedby_id` BIGINT(20) NOT NULL, + `lastmodified_date` DATETIME NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_mappings_m_floating_rates` FOREIGN KEY (`floating_rates_id`) REFERENCES `m_floating_rates` (`id`) +); + +ALTER TABLE `m_product_loan` +ADD COLUMN `is_linked_to_floating_interest_rates` BIT(1) NOT NULL DEFAULT 0 AFTER `fund_id`, +MODIFY COLUMN `nominal_interest_rate_per_period` DECIMAL(19,6) NULL DEFAULT NULL, +MODIFY COLUMN `interest_period_frequency_enum` SMALLINT(5) NULL DEFAULT NULL, +MODIFY COLUMN `annual_nominal_interest_rate` DECIMAL(19,6) NULL DEFAULT NULL; + +CREATE TABLE `m_product_loan_floating_rates` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `loan_product_id` BIGINT(20) NOT NULL, + `floating_rates_id` BIGINT(20) NOT NULL, + `interest_rate_differential` DECIMAL(19,6) NOT NULL DEFAULT 0, + `min_differential_lending_rate` DECIMAL(19,6) NOT NULL DEFAULT 0, + `default_differential_lending_rate` DECIMAL(19,6) NOT NULL DEFAULT 0, + `max_differential_lending_rate` DECIMAL(19,6) NOT NULL DEFAULT 0, + `is_floating_interest_rate_calculation_allowed` BIT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + CONSTRAINT `FK_mappings_m_product_loan_id` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`), + CONSTRAINT `FK_mappings_m_floating_rates_id` FOREIGN KEY (`floating_rates_id`) REFERENCES `m_floating_rates` (`id`) +); + +ALTER TABLE `m_loan` +ADD COLUMN `is_floating_interest_rate` BIT(1) NULL DEFAULT 0 AFTER `arrearstolerance_amount`, +ADD COLUMN `interest_rate_differential` DECIMAL(19,6) NULL DEFAULT 0 AFTER `is_floating_interest_rate`, +MODIFY COLUMN `nominal_interest_rate_per_period` DECIMAL(19,6) NULL DEFAULT NULL, +MODIFY COLUMN `interest_period_frequency_enum` SMALLINT(5) NULL DEFAULT NULL, +MODIFY COLUMN `annual_nominal_interest_rate` DECIMAL(19,6) NULL DEFAULT NULL; + +INSERT INTO `m_permission` (`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) +VALUES ('portfolio', 'READ_FLOATINGRATE', 'FLOATINGRATE', 'READ', 0), + ('portfolio', 'CREATE_FLOATINGRATE', 'FLOATINGRATE', 'CREATE', 1), + ('portfolio', 'CREATE_FLOATINGRATE_CHECKER', 'FLOATINGRATE', 'CREATE_CHECKER', 0), + ('portfolio', 'UPDATE_FLOATINGRATE', 'FLOATINGRATE', 'UPDATE', 1), + ('portfolio', 'UPDATE_FLOATINGRATE_CHECKER', 'FLOATINGRATE', 'UPDATE_CHECKER', 0);