Merge pull request #1541 from rajuan/FloatingRates

Floating Rates Feature
This commit is contained in:
pramod
2015-11-10 16:46:55 +05:30
59 changed files with 3767 additions and 460 deletions
@@ -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;
}
}
@@ -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<String> LIST_FLOATING_RATES_PARAMETERS = new HashSet<>(
Arrays.asList("id", "name", "isBaseLendingRate", "isActive",
"createdby", "createdOn", "modifiedBy", "modifiedOn"));
private static final Set<String> 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<FloatingRateData> toApiJsonSerializer;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
@Autowired
public FloatingRatesApiResource(
final PlatformSecurityContext context,
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
final DefaultToApiJsonSerializer<FloatingRateData> 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<FloatingRateData> 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);
}
}
@@ -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<FloatingRatePeriodData> baseLendingRatePeriods;
public FloatingRateDTO(final boolean isFloatingInterestRate,
final LocalDate startDate, final BigDecimal interestRateDiff,
final Collection<FloatingRatePeriodData> 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<FloatingRatePeriodData> getBaseLendingRatePeriods() {
return this.baseLendingRatePeriods;
}
}
@@ -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<FloatingRateData> {
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<FloatingRatePeriodData> ratePeriods;
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRateFrequencyTypeOptions;
public FloatingRateData(Long id, String name, boolean isBaseLendingRate,
boolean isActive, String createdBy, Date createdOn,
String modifiedBy, Date modifiedOn,
List<FloatingRatePeriodData> ratePeriods,
List<EnumOptionData> 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<FloatingRatePeriodData> 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<EnumOptionData> interestRateFrequencyTypeOptions) {
// TODO Auto-generated method stub
return new FloatingRateData(null, null, false, true, null, null, null,
null, null, interestRateFrequencyTypeOptions);
}
}
@@ -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<FloatingRatePeriodData> {
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();
}
}
@@ -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;
}
}
@@ -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<Long> {
@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<FloatingRatePeriod> 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<FloatingRatePeriod> 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<FloatingRatePeriod> 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<FloatingRatePeriod> getRatePeriods(
final AppUser currentUser, final JsonCommand command) {
if (!command.parameterExists("ratePeriods")) {
return null;
}
Set<FloatingRatePeriod> 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<String>()).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<FloatingRatePeriod> 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<String, Object> update(final JsonCommand command,
final AppUser appUser) {
final Map<String, Object> 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<FloatingRatePeriod> 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<FloatingRatePeriod> 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<FloatingRatePeriodData> fetchInterestRates(
final FloatingRateDTO floatingRateDTO) {
Collection<FloatingRatePeriodData> 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;
}
}
@@ -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<Long> {
@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());
}
}
@@ -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<FloatingRate, Long>,
JpaSpecificationExecutor<FloatingRate> {
@Query("from FloatingRate floatingRate where floatingRate.isBaseLendingRate = 1 and floatingRate.isActive = 1")
FloatingRate retrieveBaseLendingRate();
}
@@ -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);
}
}
@@ -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");
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<String> supportedParametersForFloatingRates = new HashSet<>(
Arrays.asList("name", "isBaseLendingRate", "isActive",
"ratePeriods"));
private final Set<String> 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<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
this.supportedParametersForFloatingRates);
final List<ApiParameterError> 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<LocalDate> 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<LocalDate> 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<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json,
this.supportedParametersForFloatingRates);
final List<ApiParameterError> 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<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(
"validation.msg.validation.errors.exist",
"Validation errors exist.", dataValidationErrors);
}
}
}
@@ -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);
}
@@ -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<String, Object> 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);
}
}
@@ -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<FloatingRateData> retrieveAll();
List<FloatingRateData> retrieveLookupActive();
FloatingRateData retrieveOne(Long floatingRateId);
List<FloatingRateData> retrieveAllActive();
FloatingRateData retrieveBaseLendingRate();
List<InterestRatePeriodData> retrieveInterestRatePeriods(Long floatingRateId);
}
@@ -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<FloatingRateData> retrieveAll() {
FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(false);
final String sql = "select " + rateMapper.schema();
return this.jdbcTemplate.query(sql, rateMapper);
}
@Override
public List<FloatingRateData> 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<FloatingRateData> 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<InterestRatePeriodData> 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<FloatingRateData> {
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<FloatingRatePeriodData> 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<FloatingRatePeriodData> {
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<FloatingRateData> {
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<InterestRatePeriodData> {
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();
}
}
}
@@ -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";
}
@@ -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<String> 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<String> LOAN_APPROVAL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("approvalDate", "approvalAmount"));
private final String resourceNameForPermissions = "LOAN";
@@ -354,6 +358,8 @@ public class LoansApiResource {
compoundingCalendarData);
}
Collection<InterestRatePeriodData> interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanId);
Collection<LoanTransactionData> loanRepayments = null;
LoanScheduleData repaymentSchedule = null;
Collection<LoanChargeData> 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);
@@ -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<InterestRatePeriodData> 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<InterestRatePeriodData> 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<InterestRatePeriodData> 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<InterestRatePeriodData> 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<InterestRatePeriodData> 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<ChargeData> overdueCharges = null;
final LoanScheduleData originalSchedule = null;
final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> 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<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges,
final PaidInAdvanceData paidInAdvance) {
final PaidInAdvanceData paidInAdvance, Collection<InterestRatePeriodData> 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<LoanProductData> 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<Long, Integer> 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<ChargeData> 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<InterestRatePeriodData> 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 ;
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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<Long> {
@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<LoanCharge> loanCharges,
final Set<LoanCollateral> collateral, final BigDecimal fixedEmiAmount, final Set<LoanDisbursementDetails> 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<Long> {
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final Set<LoanDisbursementDetails> 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<Long> {
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final Set<LoanDisbursementDetails> 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<Long> {
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting,
final BigDecimal fixedEmiAmount, final Set<LoanDisbursementDetails> 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<Long> {
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<Long> {
for (final LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
public boolean isInterestRecalculationEnabledForProduct() {
@@ -2578,6 +2611,23 @@ public class Loan extends AbstractPersistable<Long> {
rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod();
}
BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> 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<Long> {
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<Long> {
*/
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser);
} else {
regenerateRepaymentSchedule(generatorDTO, currentUser);
}
return processTransactions();
@@ -4929,7 +4982,7 @@ public class Loan extends AbstractPersistable<Long> {
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<Long> {
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> disbursementData = new ArrayList<>();
@@ -4967,6 +5020,23 @@ public class Loan extends AbstractPersistable<Long> {
RecalculationFrequencyType recalculationFrequencyType = this.loanInterestRecalculationDetails().getRestFrequencyType();
RecalculationFrequencyType compoundingFrequencyType = this.loanInterestRecalculationDetails().getCompoundingFrequencyType();
BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> 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<Long> {
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<Long> {
rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod();
}
BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> 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<Long> {
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<Long> {
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<Long> {
* 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<Long> {
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<Long> {
rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod();
}
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
if (loanProduct.isLinkedToFloatingInterestRate()) {
Collection<FloatingRatePeriodData> 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<Long> {
}
}
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;
}
}
@@ -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<FloatingRatePeriodData> 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;
}
@@ -54,6 +54,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, 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<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
@Param("loanType") Integer loanType);
@@ -126,5 +128,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, 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);
}
@@ -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());
}
}
@@ -32,6 +32,7 @@ public class LoanScheduleRecalculationDTO {
private final Map<LocalDate, Money> latePaymentMap;
private final TreeMap<LocalDate, Money> compoundingMap;
private final Map<LocalDate, Money> disburseDetailMap;
private final Money principalToBeScheduled;
private final Money outstandingBalance;
private final Money outstandingBalanceAsPerRest;
private final List<LoanRepaymentScheduleInstallment> 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<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
final TreeMap<LocalDate, Money> compoundingMap, final Map<LocalDate, Money> disburseDetailMap, final Money outstandingBalance,
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> recalculationDetails,
final TreeMap<LocalDate, Money> compoundingMap, final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled,
final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> 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<LocalDate, Money> principalPortionMap,
final Map<LocalDate, Money> latePaymentMap, final TreeMap<LocalDate, Money> compoundingMap,
final Map<LocalDate, Money> disburseDetailMap, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> 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<LocalDate, Money> latePaymentMap = null;
final TreeMap<LocalDate, Money> compoundingMap = null;
final Map<LocalDate, Money> disburseDetailMap = null;
final Money principalToBeScheduled = null;
final Money outstandingBalance = null;
final Money outstandingBalanceAsPerRest = null;
final List<LoanRepaymentScheduleInstallment> 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;
}
}
@@ -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<LoanTermVariationsData> 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<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms,
scheduledDueDate, transactions);
Collection<LoanTermVariationsData> 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<LoanTermVariationsData> 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<LoanTermVariationsData> getApplicableTermVariationsForPeriod(final LocalDate fromDate, final LocalDate dueDate,
final int installmentNumber, final Collection<LoanTermVariationsData> variations) {
Collection<LoanTermVariationsData> applicableVariations = new ArrayList<>();
for (LoanTermVariationsData detail : variations) {
if (detail.isApplicable(fromDate, dueDate, installmentNumber)) {
applicableVariations.add(detail);
}
}
variations.removeAll(applicableVariations);
return applicableVariations;
}
private List<LoanTransaction> createCurrentTransactionList(RecalculationDetail detail) {
List<LoanTransaction> 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<LoanTermVariationsData> 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<LoanTermVariationsData> 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<LocalDate, Money> principalVariation,
Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, int daysForInterestInFullPeriod);
Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, int daysForInterestInFullPeriod,
Collection<LoanTermVariationsData> 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<LocalDate, Money> 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);
}
@@ -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<LocalDate, Money> principalVariation, final Map<LocalDate, Money> compoundingMap,
final LocalDate periodStartDate, final LocalDate periodEndDate, final int daysForInterestInFullPeriod) {
final LocalDate periodStartDate, final LocalDate periodEndDate, final int daysForInterestInFullPeriod,
final Collection<LoanTermVariationsData> 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<LocalDate, BigDecimal> 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;
@@ -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<LocalDate, Money> principalVariation,
@SuppressWarnings("unused") Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate,
@SuppressWarnings("unused") int daysForInterestInFullPeriod) {
@SuppressWarnings("unused") int daysForInterestInFullPeriod, @SuppressWarnings("unused") Collection<LoanTermVariationsData> termVariations) {
final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays();
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator,
daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc, null);
@@ -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<LoanTermVariationsData> 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<LoanTermVariationsData> 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<LoanTermVariationsData> 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<LoanTermVariationsData> 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<LoanTermVariationsData> 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<LoanTermVariationsData> 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<LoanTermVariationsData> getLoanTermVariations() {
return this.loanTermVariations;
}
}
@@ -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);
}
@@ -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<LoanTermVariationsData> 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<FloatingRatePeriodData> baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
FloatingRateDTO floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, expectedDisbursementDate, interestRateDiff,
baseLendingRatePeriods);
Collection<FloatingRatePeriodData> 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,
@@ -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<FloatingRatePeriodData> baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
}
@@ -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:
@@ -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);
}
@@ -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<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final List<Holiday> 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<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService
.createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
List<LoanRepaymentScheduleHistory> 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<FloatingRatePeriodData> baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
}
@@ -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<String, Object> 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<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate());
final List<Holiday> 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<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> 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<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService
.retriveLoanChargesPaidBy(loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber);
Collection<LoanChargePaidByData> 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<String, Object> 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<FloatingRatePeriodData> baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
}
@@ -36,12 +36,14 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
final Set<String> 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;
@@ -48,10 +48,10 @@ public final class LoanApplicationCommandFromApiJsonHelper {
*/
final Set<String> 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<Map<String, Object>>() {}.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)
@@ -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<LoanCharge> existingCharges = existingLoanApplication.charges();
Map<Long, LoanChargeData> 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<ApiParameterError> 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); }
}
@@ -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);
}
@@ -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<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate);
Collection<Long> fetchArrearLoans();
Collection<Long> fetchLoansForInterestRecalculation();
LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate onDate);
@@ -110,4 +111,6 @@ public interface LoanReadPlatformService {
PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId);
LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(Long loanId);
}
@@ -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<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final List<Holiday> 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<PaymentTypeData> 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<EnumOptionData> amortizationTypeOptions = this.loanDropdownReadPlatformService
.retrieveLoanAmortizationTypeOptions();
final Collection<EnumOptionData> interestTypeOptions = this.loanDropdownReadPlatformService.retrieveLoanInterestTypeOptions();
Collection<EnumOptionData> interestTypeOptions = null;
if (loanProduct.isLinkedToFloatingInterestRates()) {
interestTypeOptions = Arrays.asList(interestType(InterestMethod.DECLINING_BALANCE));
} else {
interestTypeOptions = this.loanDropdownReadPlatformService.retrieveLoanInterestTypeOptions();
}
final Collection<EnumOptionData> interestCalculationPeriodTypeOptions = this.loanDropdownReadPlatformService
.retrieveLoanInterestRateCalculatedInPeriodOptions();
final Collection<FundData> 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<CalendarData> retrieveCalendars(final Long groupId) {
Collection<CalendarData> 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<Long> fetchArrearLoans() {
public Collection<Long> 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<PaymentTypeData> 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<FloatingRatePeriodData> baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
@Override
public Collection<InterestRatePeriodData> 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<InterestRatePeriodData> intRatePeriodData = new ArrayList<>();
final Collection<InterestRatePeriodData> 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());
}
}
}
@@ -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<Long> loanIds = this.loanReadPlatformService.fetchArrearLoans();
Collection<Long> loanIds = this.loanReadPlatformService.fetchLoansForInterestRecalculation();
for (Long loanId : loanIds) {
this.loanWritePlatformService.recalculateInterest(loanId);
}
@@ -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<String, Object> 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<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate();
Long overdurPenaltyWaitPeriod = null;
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> 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<Holiday> 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<FloatingRatePeriodData> baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate()
.getRatePeriods();
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods);
}
return floatingRateDTO;
}
}
@@ -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<String> 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<ProductMixData> 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<EnumOptionData> preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService
.retrivePreCloseInterestCalculationStrategyOptions();
final List<FloatingRateData> 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);
}
}
@@ -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<EnumOptionData> interestRecalculationFrequencyTypeOptions;
@SuppressWarnings("unused")
private final List<FloatingRateData> 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<EnumOptionData> valueConditionTypeOptions, final List<EnumOptionData> daysInMonthTypeOptions,
final List<EnumOptionData> daysInYearTypeOptions, final List<EnumOptionData> interestRecalculationCompoundingTypeOptions,
final List<EnumOptionData> rescheduleStrategyTypeOptions, final List<EnumOptionData> interestRecalculationFrequencyTypeOptions,
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions) {
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions, final List<FloatingRateData> 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<TransactionProcessingStrategyData> 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;
}
}
@@ -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<Long> {
@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<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator) {
final List<Charge> 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<Long> {
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<Long> {
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<Long> {
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<Long> {
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<Long> {
public Integer getAccountingType() {
return this.accountingRule;
}
public List<Charge> getLoanProductCharges(){
return this.charges;
public List<Charge> getLoanProductCharges() {
return this.charges;
}
public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) {
@@ -616,11 +658,26 @@ public class LoanProduct extends AbstractPersistable<Long> {
return this.loanConfigurableAttributes;
}
public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator) {
public Map<String, Object> update(final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) {
final Map<String, Object> 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<Long> {
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<Long> {
public LoanProductRelatedDetail getLoanProductRelatedDetail() {
return loanProductRelatedDetail;
}
public boolean isLinkedToFloatingInterestRate() {
return this.isLinkedToFloatingInterestRate;
}
public LoanProductFloatingRates getFloatingRates() {
return this.floatingRates;
}
public Collection<FloatingRatePeriodData> fetchInterestRates(final FloatingRateDTO floatingRateDTO) {
Collection<FloatingRatePeriodData> applicableRates = new ArrayList<>(1);
if (isLinkedToFloatingInterestRate()) {
applicableRates = getFloatingRates().fetchInterestRates(floatingRateDTO);
}
return applicableRates;
}
}
@@ -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<Long> {
@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<? extends String, ? extends Object> update(JsonCommand command, FloatingRate floatingRate) {
final Map<String, Object> 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<FloatingRatePeriodData> fetchInterestRates(final FloatingRateDTO floatingRateDTO) {
floatingRateDTO.addInterestRateDiff(this.interestRateDifferential);
return floatingRate.fetchInterestRates(floatingRateDTO);
}
}
@@ -147,4 +147,9 @@ public class LoanProductMinMaxConstraints {
return this.maxNumberOfRepayments;
}
public void updateForFloatingInterestRates() {
this.minNominalInterestRatePerPeriod = null;
this.maxNominalInterestRatePerPeriod = null;
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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;
@@ -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");
@@ -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);
}
}
@@ -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<Charge> 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<String, Object> 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<String, Object> 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<Charge> 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<Charge> assembleListOfProductCharges(final JsonCommand command, final String currencyCode) {
final List<Charge> charges = new ArrayList<>();
@@ -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);