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; this.href = "/provisioningentries/"+entryId;
return this; 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 interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod";
public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod"; public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod";
// Floating interest rate related
public static final String interestRateDifferentialParameterName = "interestRateDifferential";
public static final String isFloatingInterestRateParameterName = "isFloatingInterestRate";
// Error codes // 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_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"; 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 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_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 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; package org.mifosplatform.portfolio.loanaccount.api;
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestType;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; 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.client.data.ClientData;
import org.mifosplatform.portfolio.collateral.data.CollateralData; import org.mifosplatform.portfolio.collateral.data.CollateralData;
import org.mifosplatform.portfolio.collateral.service.CollateralReadPlatformService; 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.data.FundData;
import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
import org.mifosplatform.portfolio.group.data.GroupGeneralData; 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.loanaccount.service.LoanReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData; 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.LoanDropdownReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.mifosplatform.portfolio.note.data.NoteData; import org.mifosplatform.portfolio.note.data.NoteData;
@@ -105,7 +109,7 @@ import com.google.gson.JsonElement;
public class LoansApiResource { public class LoansApiResource {
private final Set<String> LOAN_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "accountNo", "status", "externalId", "clientId", 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", "loanPurposeName", "loanOfficerId", "loanOfficerName", "currency", "principal", "totalOverpaid", "inArrearsTolerance",
"termFrequency", "termPeriodFrequencyType", "numberOfRepayments", "repaymentEvery", "interestRatePerPeriod", "termFrequency", "termPeriodFrequencyType", "numberOfRepayments", "repaymentEvery", "interestRatePerPeriod",
"annualInterestRate", "repaymentFrequencyType", "transactionProcessingStrategyId", "transactionProcessingStrategyName", "annualInterestRate", "repaymentFrequencyType", "transactionProcessingStrategyId", "transactionProcessingStrategyName",
@@ -117,7 +121,7 @@ public class LoansApiResource {
"repaymentFrequencyDaysOfWeekTypeOptions", "termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions", "repaymentFrequencyDaysOfWeekTypeOptions", "termFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "fundOptions",
"repaymentStrategyOptions", "chargeOptions", "loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions", "repaymentStrategyOptions", "chargeOptions", "loanOfficerOptions", "loanPurposeOptions", "loanCollateralOptions",
"chargeTemplate", "calendarOptions", "syncDisbursementWithMeeting", "loanCounter", "loanProductCounter", "notes", "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 Set<String> LOAN_APPROVAL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("approvalDate", "approvalAmount"));
private final String resourceNameForPermissions = "LOAN"; private final String resourceNameForPermissions = "LOAN";
@@ -354,6 +358,8 @@ public class LoansApiResource {
compoundingCalendarData); compoundingCalendarData);
} }
Collection<InterestRatePeriodData> interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanId);
Collection<LoanTransactionData> loanRepayments = null; Collection<LoanTransactionData> loanRepayments = null;
LoanScheduleData repaymentSchedule = null; LoanScheduleData repaymentSchedule = null;
Collection<LoanChargeData> charges = null; Collection<LoanChargeData> charges = null;
@@ -488,7 +494,11 @@ public class LoansApiResource {
interestRateFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); interestRateFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions();
amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); 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(); interestCalculationPeriodTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestRateCalculatedInPeriodOptions();
fundOptions = this.fundReadPlatformService.retrieveAllFunds(); fundOptions = this.fundReadPlatformService.retrieveAllFunds();
@@ -537,7 +547,7 @@ public class LoansApiResource {
null, null, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, null, null, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions,
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations, loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
overdueCharges, paidInAdvanceTemplate); overdueCharges, paidInAdvanceTemplate, interestRatesPeriods);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters); mandatoryResponseParameters);
@@ -27,6 +27,7 @@ import org.mifosplatform.portfolio.account.data.PortfolioAccountData;
import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.calendar.data.CalendarData;
import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.charge.data.ChargeData;
import org.mifosplatform.portfolio.collateral.data.CollateralData; 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.fund.data.FundData;
import org.mifosplatform.portfolio.group.data.GroupGeneralData; import org.mifosplatform.portfolio.group.data.GroupGeneralData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
@@ -64,6 +65,7 @@ public class LoanAccountData {
private final Long loanProductId; private final Long loanProductId;
private final String loanProductName; private final String loanProductName;
private final String loanProductDescription; private final String loanProductDescription;
private final boolean isLoanProductLinkedToFloatingRate;
private final Long fundId; private final Long fundId;
private final String fundName; private final String fundName;
private final Long loanPurposeId; private final Long loanPurposeId;
@@ -88,6 +90,8 @@ public class LoanAccountData {
private final BigDecimal interestRatePerPeriod; private final BigDecimal interestRatePerPeriod;
private final EnumOptionData interestRateFrequencyType; private final EnumOptionData interestRateFrequencyType;
private final BigDecimal annualInterestRate; private final BigDecimal annualInterestRate;
private final boolean isFloatingInterestRate;
private final BigDecimal interestRateDifferential;
// settings // settings
private final EnumOptionData amortizationType; private final EnumOptionData amortizationType;
@@ -182,6 +186,8 @@ public class LoanAccountData {
// Paid In Advance // Paid In Advance
private final PaidInAdvanceData paidInAdvance; private final PaidInAdvanceData paidInAdvance;
private final Collection<InterestRatePeriodData> interestRatesPeriods;
/** /**
* Used to produce a {@link LoanAccountData} with only collateral options * Used to produce a {@link LoanAccountData} with only collateral options
@@ -200,6 +206,7 @@ public class LoanAccountData {
final Long loanProductId = null; final Long loanProductId = null;
final String loanProductName = null; final String loanProductName = null;
final String loanProductDescription = null; final String loanProductDescription = null;
final boolean isLoanProductLinkedToFloatingRate = false;
final Long fundId = null; final Long fundId = null;
final String fundName = null; final String fundName = null;
final Long loanPurposeId = null; final Long loanPurposeId = null;
@@ -225,6 +232,8 @@ public class LoanAccountData {
final EnumOptionData interestRateFrequencyType = null; final EnumOptionData interestRateFrequencyType = null;
final BigDecimal annualInterestRate = null; final BigDecimal annualInterestRate = null;
final EnumOptionData interestType = null; final EnumOptionData interestType = null;
final boolean isFloatingInterestRate = false;
final BigDecimal interestRateDifferential = null;
final EnumOptionData interestCalculationPeriodType = null; final EnumOptionData interestCalculationPeriodType = null;
final LocalDate expectedFirstRepaymentOnDate = null; final LocalDate expectedFirstRepaymentOnDate = null;
final Integer graceOnPrincipalPayment = null; final Integer graceOnPrincipalPayment = null;
@@ -283,13 +292,15 @@ public class LoanAccountData {
final LoanScheduleData originalSchedule = null; final LoanScheduleData originalSchedule = null;
final Boolean createStandingInstructionAtDisbursement = null; final Boolean createStandingInstructionAtDisbursement = null;
final PaidInAdvanceData paidInAdvance = null; final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, 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, currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType,
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate,
interestRateDifferential, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
@@ -300,7 +311,7 @@ public class LoanAccountData {
disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance); createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
} }
@@ -320,6 +331,7 @@ public class LoanAccountData {
final Long loanProductId = null; final Long loanProductId = null;
final String loanProductName = null; final String loanProductName = null;
final String loanProductDescription = null; final String loanProductDescription = null;
final boolean isLoanProductLinkedToFloatingRate = false;
final Long fundId = null; final Long fundId = null;
final String fundName = null; final String fundName = null;
final Long loanPurposeId = null; final Long loanPurposeId = null;
@@ -345,6 +357,8 @@ public class LoanAccountData {
final EnumOptionData interestRateFrequencyType = null; final EnumOptionData interestRateFrequencyType = null;
final BigDecimal annualInterestRate = null; final BigDecimal annualInterestRate = null;
final EnumOptionData interestType = null; final EnumOptionData interestType = null;
final boolean isFloatingInterestRate = false;
final BigDecimal interestRateDifferential = null;
final EnumOptionData interestCalculationPeriodType = null; final EnumOptionData interestCalculationPeriodType = null;
final LocalDate expectedFirstRepaymentOnDate = null; final LocalDate expectedFirstRepaymentOnDate = null;
final Integer graceOnPrincipalPayment = null; final Integer graceOnPrincipalPayment = null;
@@ -404,13 +418,15 @@ public class LoanAccountData {
final LoanScheduleData originalSchedule = null; final LoanScheduleData originalSchedule = null;
final Boolean createStandingInstructionAtDisbursement = null; final Boolean createStandingInstructionAtDisbursement = null;
final PaidInAdvanceData paidInAdvance = null; final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, 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, currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType,
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
@@ -421,20 +437,21 @@ public class LoanAccountData {
disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingLoanBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance); createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
} }
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) { 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, 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.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.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, 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.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
clientAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, clientAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, 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.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, 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 Long loanProductId = null;
final String loanProductName = null; final String loanProductName = null;
final String loanProductDescription = null; final String loanProductDescription = null;
final boolean isLoanProductLinkedToFloatingRate = false;
final Long fundId = null; final Long fundId = null;
final String fundName = null; final String fundName = null;
final Long loanPurposeId = null; final Long loanPurposeId = null;
@@ -492,6 +510,8 @@ public class LoanAccountData {
final EnumOptionData interestRateFrequencyType = null; final EnumOptionData interestRateFrequencyType = null;
final BigDecimal annualInterestRate = null; final BigDecimal annualInterestRate = null;
final EnumOptionData interestType = null; final EnumOptionData interestType = null;
final boolean isFloatingInterestRate = false;
final BigDecimal interestRateDifferential = null;
final EnumOptionData interestCalculationPeriodType = null; final EnumOptionData interestCalculationPeriodType = null;
final LocalDate expectedFirstRepaymentOnDate = null; final LocalDate expectedFirstRepaymentOnDate = null;
final Integer graceOnPrincipalPayment = null; final Integer graceOnPrincipalPayment = null;
@@ -551,13 +571,15 @@ public class LoanAccountData {
final LoanScheduleData originalSchedule = null; final LoanScheduleData originalSchedule = null;
final Boolean createStandingInstructionAtDisbursement = null; final Boolean createStandingInstructionAtDisbursement = null;
final PaidInAdvanceData paidInAdvance = null; final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, 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, currencyData, proposedPrincipal, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType,
interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType,
expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged,
interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges,
collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
@@ -568,20 +590,21 @@ public class LoanAccountData {
disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingBalance, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, maxOutstandingBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance); createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
} }
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) { 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, 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.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, 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.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
groupAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, groupAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, 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.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, 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 LoanScheduleData originalSchedule = null;
final Boolean createStandingInstructionAtDisbursement = null; final Boolean createStandingInstructionAtDisbursement = null;
final PaidInAdvanceData paidInAdvance = null; final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, 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, loanPurposeName, loanOfficerId, loanOfficerName, product.getCurrency(), proposedPrincipal, principal, principal,
totalOverpaid, product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, numberOfRepayments, totalOverpaid, product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, numberOfRepayments,
product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(),
transactionProcessingStrategyName, product.getAmortizationType(), interestRatePerPeriod, transactionProcessingStrategyName, product.getAmortizationType(), interestRatePerPeriod,
product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(), product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(),
product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(),
product.getInterestCalculationPeriodType(), expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getInterestCalculationPeriodType(), expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(),
product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary, product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary,
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
@@ -732,7 +757,7 @@ public class LoanAccountData {
product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears, product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears,
product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(), product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(),
product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(),
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance); originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
} }
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) { 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, 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.getFundName(), acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName,
product.getCurrency(), product.getPrincipal(), product.getPrincipal(), product.getPrincipal(), acc.totalOverpaid, product.getCurrency(), product.getPrincipal(), product.getPrincipal(), product.getPrincipal(), acc.totalOverpaid,
product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(), product.getInArrearsTolerance(), termFrequency, termPeriodFrequencyType, product.getNumberOfRepayments(),
product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(), product.getRepaymentEvery(), product.getRepaymentFrequencyType(), null, null, product.getTransactionProcessingStrategyId(),
product.getTransactionProcessingStrategyName(), product.getAmortizationType(), product.getInterestRatePerPeriod(), product.getTransactionProcessingStrategyName(), product.getAmortizationType(), product.getInterestRatePerPeriod(),
product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(), product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(),
product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(),
product.getInterestCalculationPeriodType(), acc.expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getInterestCalculationPeriodType(), acc.expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(),
product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), acc.interestChargedFromDate, acc.timeline, product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), acc.interestChargedFromDate, acc.timeline,
acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors,
@@ -787,7 +813,8 @@ public class LoanAccountData {
product.canDefineInstallmentAmount(), acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations, product.canDefineInstallmentAmount(), acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations,
acc.memberVariations, product, acc.inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, acc.memberVariations, product, acc.inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA,
product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(), 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, 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 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 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 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 String loanOfficerName, final CurrencyData currencyData, final BigDecimal proposedPrincipal, final BigDecimal principal,
final BigDecimal approvedPrincipal, final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance, final BigDecimal approvedPrincipal, final BigDecimal totalOverpaid, final BigDecimal inArrearsTolerance,
@@ -805,7 +833,8 @@ public class LoanAccountData {
final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, EnumOptionData repaymentFrequencyNthDayType, final Integer repaymentEvery, final EnumOptionData repaymentFrequencyType, EnumOptionData repaymentFrequencyNthDayType,
EnumOptionData repaymentFrequencyDayOfWeekType, final Long transactionStrategyId, final String transactionStrategyName, EnumOptionData repaymentFrequencyDayOfWeekType, final Long transactionStrategyId, final String transactionStrategyName,
final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType, 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 LocalDate expectedFirstRepaymentOnDate, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment,
final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline,
final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged, final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged,
@@ -848,13 +877,15 @@ public class LoanAccountData {
final Collection<ChargeData> overdueCharges = null; final Collection<ChargeData> overdueCharges = null;
final LoanScheduleData originalSchedule = null; final LoanScheduleData originalSchedule = null;
final PaidInAdvanceData paidInAdvance = null; final PaidInAdvanceData paidInAdvance = null;
final Collection<InterestRatePeriodData> interestRatesPeriods = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, 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, currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance, termFrequency,
termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType,
repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod,
interestRateFrequencyType, annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential,
interestCalculationPeriodType, expectedFirstRepaymentOnDate,
graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary,
feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData,
productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions,
@@ -865,7 +896,7 @@ public class LoanAccountData {
disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmont, outstandingLoanBalance, disbursementData, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmont, outstandingLoanBalance,
emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType,
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, 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 Collection<NoteData> notes, final Collection<PortfolioAccountData> accountLinkingOptions,
final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails, final PortfolioAccountData linkedAccount, final Collection<DisbursementData> disbursementDetails,
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges, final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges,
final PaidInAdvanceData paidInAdvance) { final PaidInAdvanceData paidInAdvance, Collection<InterestRatePeriodData> interestRatesPeriods) {
LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; LoanProductConfigurableAttributes loanProductConfigurableAttributes = null;
if (acc.product != null) { if (acc.product != null) {
loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes(); loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes();
} }
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, 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.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, 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.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral,
guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions,
@@ -913,7 +945,7 @@ public class LoanAccountData {
acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule,
acc.createStandingInstructionAtDisbursement, paidInAdvance); acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods);
} }
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions, 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.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions,
acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null,
allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions, 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) { 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, 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.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, 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.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, 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.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, 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) { 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, 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.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, 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.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, 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.maxOutstandingLoanBalance, acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, 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( final LoanInterestRecalculationData interestRecalculationData = LoanInterestRecalculationData.withCalendarData(
acc.interestRecalculationData, calendarData, compoundingCalendarData); acc.interestRecalculationData, calendarData, compoundingCalendarData);
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, 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.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, 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.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, 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.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule, 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) { 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, 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.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.proposedPrincipal,
acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency,
acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType,
acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId, acc.repaymentFrequencyNthDayType, acc.repaymentFrequencyDayOfWeekType, acc.transactionProcessingStrategyId,
acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, 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.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate,
acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges,
acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, 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.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears,
acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType,
acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule,
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods);
} }
private LoanAccountData( private LoanAccountData(
@@ -1080,6 +1116,7 @@ public class LoanAccountData {
final Long loanProductId, final Long loanProductId,
final String loanProductName, final String loanProductName,
final String loanProductDescription, // final String loanProductDescription, //
final boolean isLoanProductLinkedToFloatingRate,
final Long fundId, final Long fundId,
final String fundName, final String fundName,
final Long loanPurposeId, final Long loanPurposeId,
@@ -1100,7 +1137,8 @@ public class LoanAccountData {
final EnumOptionData repaymentFrequencyNthDayType, final EnumOptionData repaymentFrequencyDayOfWeekType, final EnumOptionData repaymentFrequencyNthDayType, final EnumOptionData repaymentFrequencyDayOfWeekType,
final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName, final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName,
final EnumOptionData amortizationType, final BigDecimal interestRatePerPeriod, final EnumOptionData interestRateFrequencyType, 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 LocalDate expectedFirstRepaymentOnDate, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment,
final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline,
final LoanSummaryData summary, final BigDecimal feeChargesDueAtDisbursementCharged, final LoanScheduleData repaymentSchedule, 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 Integer graceOnArrearsAgeing, final Collection<ChargeData> overdueCharges, final Boolean isNPA,
final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled,
final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule, 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.id = id;
this.accountNo = accountNo; this.accountNo = accountNo;
@@ -1139,6 +1178,7 @@ public class LoanAccountData {
this.loanProductId = loanProductId; this.loanProductId = loanProductId;
this.loanProductName = loanProductName; this.loanProductName = loanProductName;
this.loanProductDescription = loanProductDescription; this.loanProductDescription = loanProductDescription;
this.isLoanProductLinkedToFloatingRate = isLoanProductLinkedToFloatingRate;
this.fundId = fundId; this.fundId = fundId;
this.fundName = fundName; this.fundName = fundName;
this.loanPurposeId = loanPurposeId; this.loanPurposeId = loanPurposeId;
@@ -1165,6 +1205,8 @@ public class LoanAccountData {
this.interestRateFrequencyType = interestRateFrequencyType; this.interestRateFrequencyType = interestRateFrequencyType;
this.annualInterestRate = annualInterestRate; this.annualInterestRate = annualInterestRate;
this.interestType = interestType; this.interestType = interestType;
this.isFloatingInterestRate = isFloatingInterestRate;
this.interestRateDifferential = interestRateDifferential;
this.interestCalculationPeriodType = interestCalculationPeriodType; this.interestCalculationPeriodType = interestCalculationPeriodType;
this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate; this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate;
this.graceOnPrincipalPayment = graceOnPrincipalPayment; this.graceOnPrincipalPayment = graceOnPrincipalPayment;
@@ -1289,6 +1331,7 @@ public class LoanAccountData {
if (this.product != null) { if (this.product != null) {
this.product.setloanProductConfigurableAttributes(product.getloanProductConfigurableAttributes()); this.product.setloanProductConfigurableAttributes(product.getloanProductConfigurableAttributes());
} }
this.interestRatesPeriods = interestRatesPeriods;
} }
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() { public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
@@ -1395,4 +1438,5 @@ public class LoanAccountData {
if(this.summary != null) return this.summary.getTotalPaidFeeCharges() ; if(this.summary != null) return this.summary.getTotalPaidFeeCharges() ;
return BigDecimal.ZERO ; return BigDecimal.ZERO ;
} }
} }
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.infrastructure.core.data.EnumOptionData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType;
public class LoanTermVariationsData { public class LoanTermVariationsData {
@@ -16,6 +17,7 @@ public class LoanTermVariationsData {
private final Long id; private final Long id;
private final EnumOptionData termType; private final EnumOptionData termType;
private final LocalDate termVariationApplicableFrom; private final LocalDate termVariationApplicableFrom;
private final Integer termVariationApplicableFromInstallment;
private final BigDecimal termValue; private final BigDecimal termValue;
public LoanTermVariationsData(final Long id, final EnumOptionData termType, final LocalDate termVariationApplicableFrom, public LoanTermVariationsData(final Long id, final EnumOptionData termType, final LocalDate termVariationApplicableFrom,
@@ -24,11 +26,25 @@ public class LoanTermVariationsData {
this.termType = termType; this.termType = termType;
this.termVariationApplicableFrom = termVariationApplicableFrom; this.termVariationApplicableFrom = termVariationApplicableFrom;
this.termValue = termValue; 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() { public EnumOptionData getTermType() {
return this.termType; return this.termType;
} }
public LoanTermVariationType getTermVariationType(){
return LoanTermVariationType.fromInt(this.termType.getId().intValue());
}
public LocalDate getTermApplicableFrom() { public LocalDate getTermApplicableFrom() {
return this.termVariationApplicableFrom; return this.termVariationApplicableFrom;
@@ -37,4 +53,30 @@ public class LoanTermVariationsData {
public BigDecimal getTermValue() { public BigDecimal getTermValue() {
return this.termValue; 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.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.floatingrates.data.FloatingRateDTO;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
public class ScheduleGeneratorDTO { public class ScheduleGeneratorDTO {
@@ -20,10 +21,12 @@ public class ScheduleGeneratorDTO {
final CalendarInstance compoundingCalendarInstance; final CalendarInstance compoundingCalendarInstance;
LocalDate recalculateFrom; LocalDate recalculateFrom;
final Long overdurPenaltyWaitPeriod; final Long overdurPenaltyWaitPeriod;
final FloatingRateDTO floatingRateDTO;
public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO, 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.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrency = applicationCurrency; this.applicationCurrency = applicationCurrency;
@@ -33,13 +36,14 @@ public class ScheduleGeneratorDTO {
this.recalculateFrom = null; this.recalculateFrom = null;
this.overdurPenaltyWaitPeriod = null; this.overdurPenaltyWaitPeriod = null;
this.holidayDetailDTO = holidayDetailDTO; this.holidayDetailDTO = holidayDetailDTO;
this.floatingRateDTO = floatingRateDTO;
} }
public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO, final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO,
final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance, 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.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrency = applicationCurrency; this.applicationCurrency = applicationCurrency;
@@ -49,7 +53,7 @@ public class ScheduleGeneratorDTO {
this.recalculateFrom = recalculateFrom; this.recalculateFrom = recalculateFrom;
this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod; this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod;
this.holidayDetailDTO = holidayDetailDTO; this.holidayDetailDTO = holidayDetailDTO;
this.floatingRateDTO = floatingRateDTO;
} }
public LoanScheduleGeneratorFactory getLoanScheduleFactory() { public LoanScheduleGeneratorFactory getLoanScheduleFactory() {
@@ -96,4 +100,9 @@ public class ScheduleGeneratorDTO {
return this.compoundingCalendarInstance; 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.DayOfWeekType;
import org.mifosplatform.portfolio.common.domain.NthDayType; import org.mifosplatform.portfolio.common.domain.NthDayType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; 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.fund.domain.Fund;
import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
@@ -369,18 +371,26 @@ public class Loan extends AbstractPersistable<Long> {
@Column(name = "interest_recalcualated_on") @Column(name = "interest_recalcualated_on")
private Date interestRecalculatedOn; 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, 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 LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Set<LoanCollateral> collateral, final BigDecimal fixedEmiAmount, final Set<LoanDisbursementDetails> disbursementDetails, 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 LoanStatus status = null;
final Group group = null; final Group group = null;
final Boolean syncDisbursementWithMeeting = null; final Boolean syncDisbursementWithMeeting = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, 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, 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 LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final Set<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Set<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement) { final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential) {
final LoanStatus status = null; final LoanStatus status = null;
final Client client = null; final Client client = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, 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, 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 LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set<LoanCharge> loanCharges,
final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final Set<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Set<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement) { final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential) {
final LoanStatus status = null; final LoanStatus status = null;
return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct,
loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement); disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential);
} }
protected Loan() { protected Loan() {
@@ -419,11 +433,15 @@ public class Loan extends AbstractPersistable<Long> {
final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus, final LoanProduct loanProduct, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final LoanStatus loanStatus,
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting,
final BigDecimal fixedEmiAmount, final Set<LoanDisbursementDetails> disbursementDetails, 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 = loanRepaymentScheduleDetail;
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings(); this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
this.isFloatingInterestRate = isFloatingInterestRate;
this.interestRateDifferential = interestRateDifferential;
if (StringUtils.isBlank(accountNo)) { if (StringUtils.isBlank(accountNo)) {
this.accountNumber = new RandomPasswordGenerator(19).generate(); this.accountNumber = new RandomPasswordGenerator(19).generate();
this.accountNumberRequiresAutoGeneration = true; this.accountNumberRequiresAutoGeneration = true;
@@ -1288,6 +1306,20 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put("recalculateLoanSchedule", true); 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; Long existingFundId = null;
if (this.fund != null) { if (this.fund != null) {
existingFundId = this.fund.getId(); existingFundId = this.fund.getId();
@@ -1498,6 +1530,7 @@ public class Loan extends AbstractPersistable<Long> {
for (final LoanCharge loanCharge : charges) { for (final LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge, penaltyWaitPeriod); recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
} }
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
public boolean isInterestRecalculationEnabledForProduct() { public boolean isInterestRecalculationEnabledForProduct() {
@@ -2578,6 +2611,23 @@ public class Loan extends AbstractPersistable<Long> {
rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); 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(), final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(),
loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), loanTermFrequency, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(),
getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), getExpectedFirstRepaymentOnDate(), scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(),
@@ -2585,7 +2635,8 @@ public class Loan extends AbstractPersistable<Long> {
this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(), this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate(),
this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(),
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, 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(), final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
scheduleGeneratorDTO.getHolidayDetailDTO()); scheduleGeneratorDTO.getHolidayDetailDTO());
@@ -4839,6 +4890,8 @@ public class Loan extends AbstractPersistable<Long> {
*/ */
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser); regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO, currentUser);
} else {
regenerateRepaymentSchedule(generatorDTO, currentUser);
} }
return processTransactions(); return processTransactions();
@@ -4929,7 +4982,7 @@ public class Loan extends AbstractPersistable<Long> {
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO.getApplicationCurrency(), final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO.getApplicationCurrency(),
generatorDTO.getCalculatedRepaymentsStartingFromDate(), generatorDTO.getCalendarInstanceForInterestRecalculation(), generatorDTO.getCalculatedRepaymentsStartingFromDate(), generatorDTO.getCalendarInstanceForInterestRecalculation(),
generatorDTO.getCompoundingCalendarInstance()); generatorDTO.getCompoundingCalendarInstance(), generatorDTO.getFloatingRateDTO());
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
@@ -4941,7 +4994,7 @@ public class Loan extends AbstractPersistable<Long> {
public LoanApplicationTerms constructLoanApplicationTerms(final ApplicationCurrency applicationCurrency, public LoanApplicationTerms constructLoanApplicationTerms(final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation, final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation,
final CalendarInstance compoundingCalendarInstance) { final CalendarInstance compoundingCalendarInstance, final FloatingRateDTO floatingRateDTO) {
final Integer loanTermFrequency = this.termFrequency; final Integer loanTermFrequency = this.termFrequency;
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
final List<DisbursementData> disbursementData = new ArrayList<>(); final List<DisbursementData> disbursementData = new ArrayList<>();
@@ -4967,6 +5020,23 @@ public class Loan extends AbstractPersistable<Long> {
RecalculationFrequencyType recalculationFrequencyType = this.loanInterestRecalculationDetails().getRestFrequencyType(); RecalculationFrequencyType recalculationFrequencyType = this.loanInterestRecalculationDetails().getRestFrequencyType();
RecalculationFrequencyType compoundingFrequencyType = this.loanInterestRecalculationDetails().getCompoundingFrequencyType(); 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, final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
@@ -4975,13 +5045,13 @@ public class Loan extends AbstractPersistable<Long> {
calendarInstanceForInterestRecalculation, recalculationFrequencyType, compoundingCalendarInstance, calendarInstanceForInterestRecalculation, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(), compoundingFrequencyType, this.loanProduct.getPrincipalThresholdForLastInstallment(),
this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy(), this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.preCloseInterestCalculationStrategy(),
getApprovedPrincipal()); getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations);
return loanApplicationTerms; return loanApplicationTerms;
} }
public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory, public LoanRepaymentScheduleInstallment fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory,
final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance,
final HolidayDetailDTO holidayDetailDTO, final LocalDate onDate) { final HolidayDetailDTO holidayDetailDTO, final LocalDate onDate, FloatingRateDTO floatingRateDTO) {
LoanRepaymentScheduleInstallment installment = null; LoanRepaymentScheduleInstallment installment = null;
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
@@ -5028,6 +5098,23 @@ public class Loan extends AbstractPersistable<Long> {
rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); 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, final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(null, loanTermFrequency,
loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null, loanTermPeriodFrequencyType, nthDayType, dayOfWeekType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null,
getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount,
@@ -5035,7 +5122,7 @@ public class Loan extends AbstractPersistable<Long> {
this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(),
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance,
compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod,
getApprovedPrincipal()); getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(), installment = loanScheduleGenerator.calculatePrepaymentAmount(getCurrency(), onDate, loanApplicationTerms, mc, charges(),
@@ -5076,7 +5163,7 @@ public class Loan extends AbstractPersistable<Long> {
public LocalDate fetchInterestRecalculateFromDate() { public LocalDate fetchInterestRecalculateFromDate() {
LocalDate interestRecalculatedOn = null; LocalDate interestRecalculatedOn = null;
if (this.interestRecalculatedOn != null) { if (this.interestRecalculatedOn == null) {
interestRecalculatedOn = getDisbursementDate(); interestRecalculatedOn = getDisbursementDate();
} else { } else {
interestRecalculatedOn = new LocalDate(this.interestRecalculatedOn); interestRecalculatedOn = new LocalDate(this.interestRecalculatedOn);
@@ -5189,13 +5276,16 @@ public class Loan extends AbstractPersistable<Long> {
* TODO * TODO
* @param compoundingCalendarInstance * @param compoundingCalendarInstance
* TODO * TODO
* @param floatingRateDTO
* TODO
* @param loanCalendarInstance * @param loanCalendarInstance
* Used for accessing the loan's calendar object * Used for accessing the loan's calendar object
* @return application terms of the Loan object * @return application terms of the Loan object
**/ **/
@SuppressWarnings({ "unused" }) @SuppressWarnings({ "unused" })
public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency, 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(); LoanProduct loanProduct = loanProduct();
// LoanProductRelatedDetail loanProductRelatedDetail = // LoanProductRelatedDetail loanProductRelatedDetail =
// getLoanRepaymentScheduleDetail(); // getLoanRepaymentScheduleDetail();
@@ -5219,7 +5309,7 @@ public class Loan extends AbstractPersistable<Long> {
final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod(); final BigDecimal interestRatePerPeriod = this.loanRepaymentScheduleDetail.getNominalInterestRatePerPeriod();
final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(); final PeriodFrequencyType interestRatePeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType();
final BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
final Money principalMoney = this.loanRepaymentScheduleDetail.getPrincipal(); final Money principalMoney = this.loanRepaymentScheduleDetail.getPrincipal();
final LocalDate expectedDisbursementDate = getExpectedDisbursedOnLocalDate(); final LocalDate expectedDisbursementDate = getExpectedDisbursedOnLocalDate();
@@ -5264,13 +5354,30 @@ public class Loan extends AbstractPersistable<Long> {
rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); 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, return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDayType,
dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, dayOfWeekType, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate,
inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData, inArrearsToleranceMoney, this.loanRepaymentScheduleDetail, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementData,
maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate, maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate,
this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(),
recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, 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.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService; 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.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
@@ -88,6 +91,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final LoanAccrualPlatformService loanAccrualPlatformService; private final LoanAccrualPlatformService loanAccrualPlatformService;
private final PlatformSecurityContext context; private final PlatformSecurityContext context;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired @Autowired
public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository,
@@ -101,7 +105,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final CalendarInstanceRepository calendarInstanceRepository, final CalendarInstanceRepository calendarInstanceRepository,
final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository, final LoanRepaymentScheduleInstallmentRepository repaymentScheduleInstallmentRepository,
final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context, final LoanAccrualPlatformService loanAccrualPlatformService, final PlatformSecurityContext context,
final BusinessEventNotifierService businessEventNotifierService) { final BusinessEventNotifierService businessEventNotifierService,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.loanAccountAssembler = loanAccountAssembler; this.loanAccountAssembler = loanAccountAssembler;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
@@ -119,6 +124,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.loanAccrualPlatformService = loanAccrualPlatformService; this.loanAccrualPlatformService = loanAccrualPlatformService;
this.context = context; this.context = context;
this.businessEventNotifierService = businessEventNotifierService; this.businessEventNotifierService = businessEventNotifierService;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
} }
@Transactional @Transactional
@@ -190,9 +196,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay); allowTransactionsOnNonWorkingDay);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
@@ -239,6 +247,19 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
return newRepaymentTransaction; 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) { private void saveLoanTransactionWithDataIntegrityViolationChecks(LoanTransaction newRepaymentTransaction) {
try { try {
this.loanTransactionRepository.save(newRepaymentTransaction); this.loanTransactionRepository.save(newRepaymentTransaction);
@@ -557,8 +578,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
} }
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, floatingRateDTO);
return scheduleGeneratorDTO; 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_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) @Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId, List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId,
@Param("loanType") Integer loanType); @Param("loanType") Integer loanType);
@@ -126,5 +128,8 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
@Query(DOES_CLIENT_HAVE_NON_CLOSED_LOANS) @Query(DOES_CLIENT_HAVE_NON_CLOSED_LOANS)
boolean doNonClosedLoanAccountsExistForClient(@Param("clientId") Long clientId); 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 { public enum LoanTermVariationType {
INVALID(0, "loanTermType.invalid"), // 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 Integer value;
private final String code; private final String code;
@@ -33,6 +34,9 @@ public enum LoanTermVariationType {
case 1: case 1:
enumeration = LoanTermVariationType.EMI_AMOUNT; enumeration = LoanTermVariationType.EMI_AMOUNT;
break; break;
case 2:
enumeration = LoanTermVariationType.INTEREST_RATE;
break;
} }
return enumeration; return enumeration;
} }
@@ -40,4 +44,8 @@ public enum LoanTermVariationType {
public boolean isEMIAmountVariation() { public boolean isEMIAmountVariation() {
return this.value.equals(LoanTermVariationType.EMI_AMOUNT.getValue()); 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 Map<LocalDate, Money> latePaymentMap;
private final TreeMap<LocalDate, Money> compoundingMap; private final TreeMap<LocalDate, Money> compoundingMap;
private final Map<LocalDate, Money> disburseDetailMap; private final Map<LocalDate, Money> disburseDetailMap;
private final Money principalToBeScheduled;
private final Money outstandingBalance; private final Money outstandingBalance;
private final Money outstandingBalanceAsPerRest; private final Money outstandingBalanceAsPerRest;
private final List<LoanRepaymentScheduleInstallment> installments; private final List<LoanRepaymentScheduleInstallment> installments;
@@ -44,9 +45,9 @@ public class LoanScheduleRecalculationDTO {
final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected,
final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap, 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 TreeMap<LocalDate, Money> compoundingMap, final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled,
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final Collection<RecalculationDetail> recalculationDetails, final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean partialUpdate) { final boolean partialUpdate) {
this.periodNumber = periodNumber; this.periodNumber = periodNumber;
@@ -63,6 +64,7 @@ public class LoanScheduleRecalculationDTO {
this.latePaymentMap = latePaymentMap; this.latePaymentMap = latePaymentMap;
this.compoundingMap = compoundingMap; this.compoundingMap = compoundingMap;
this.disburseDetailMap = disburseDetailMap; this.disburseDetailMap = disburseDetailMap;
this.principalToBeScheduled = principalToBeScheduled;
this.outstandingBalance = outstandingBalance; this.outstandingBalance = outstandingBalance;
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest; this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
this.installments = installments; this.installments = installments;
@@ -77,14 +79,15 @@ public class LoanScheduleRecalculationDTO {
final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
final Money totalRepaymentExpected, final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap, 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> latePaymentMap, final TreeMap<LocalDate, Money> compoundingMap,
final Map<LocalDate, Money> disburseDetailMap, final Money outstandingBalance, final Money outstandingBalanceAsPerRest, final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails, final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate) {
final boolean partialUpdate = true; final boolean partialUpdate = true;
return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap, totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap,
outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate); loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate);
} }
@@ -105,6 +108,7 @@ public class LoanScheduleRecalculationDTO {
final Map<LocalDate, Money> latePaymentMap = null; final Map<LocalDate, Money> latePaymentMap = null;
final TreeMap<LocalDate, Money> compoundingMap = null; final TreeMap<LocalDate, Money> compoundingMap = null;
final Map<LocalDate, Money> disburseDetailMap = null; final Map<LocalDate, Money> disburseDetailMap = null;
final Money principalToBeScheduled = null;
final Money outstandingBalance = null; final Money outstandingBalance = null;
final Money outstandingBalanceAsPerRest = null; final Money outstandingBalanceAsPerRest = null;
final List<LoanRepaymentScheduleInstallment> installments = null; final List<LoanRepaymentScheduleInstallment> installments = null;
@@ -112,7 +116,7 @@ public class LoanScheduleRecalculationDTO {
return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, return new LoanScheduleRecalculationDTO(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap, totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap,
outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, installments, recalculationDetails,
loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate); loanRepaymentScheduleTransactionProcessor, scheduleTillDate, partialUpdate);
} }
@@ -200,4 +204,9 @@ public class LoanScheduleRecalculationDTO {
return this.periodStartDate; 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.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; 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.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; 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.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -181,6 +183,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
extendTermForDailyRepayments = true; extendTermForDailyRepayments = true;
} }
// for applying variations
Collection<LoanTermVariationsData> loanTermVariations = new ArrayList<>(loanApplicationTerms.getLoanTermVariations());
// this block is to start the schedule generation from specified date // this block is to start the schedule generation from specified date
if (loanScheduleRecalculationDTO != null && loanScheduleRecalculationDTO.isPartialUpdate()) { if (loanScheduleRecalculationDTO != null && loanScheduleRecalculationDTO.isPartialUpdate()) {
periodNumber = loanScheduleRecalculationDTO.getPeriodNumber(); periodNumber = loanScheduleRecalculationDTO.getPeriodNumber();
@@ -206,6 +211,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
outstandingBalanceAsPerRest = loanScheduleRecalculationDTO.getOutstandingBalanceAsPerRest(); outstandingBalanceAsPerRest = loanScheduleRecalculationDTO.getOutstandingBalanceAsPerRest();
installments.clear(); installments.clear();
installments.addAll(loanScheduleRecalculationDTO.getInstallments()); 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()) { while (!outstandingBalance.isZero() || !disburseDetailMap.isEmpty()) {
@@ -241,6 +256,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms, Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(loanApplicationTerms,
scheduledDueDate, transactions); scheduledDueDate, transactions);
Collection<LoanTermVariationsData> applicableVariations = getApplicableTermVariationsForPeriod(periodStartDate,
scheduledDueDate, periodNumber, loanTermVariations);
double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate, .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate,
loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(),
@@ -320,7 +338,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc,
mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap),
compoundingMap, periodStartDateApplicableForInterest, detail.getTransactionDate(), compoundingMap, periodStartDateApplicableForInterest, detail.getTransactionDate(),
daysInPeriodApplicableForInterest); daysInPeriodApplicableForInterest, applicableVariations);
interestForThisinstallment = principalInterestForThisPeriod.interest(); interestForThisinstallment = principalInterestForThisPeriod.interest();
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
@@ -461,7 +479,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
mc, mc,
mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap),
compoundingMap, periodStartDateApplicableForInterest, calculateTill, compoundingMap, periodStartDateApplicableForInterest, calculateTill,
daysInPeriodApplicableForInterest); daysInPeriodApplicableForInterest, applicableVariations);
if (!principalInterestForThisPeriod.interest() if (!principalInterestForThisPeriod.interest()
.plus(principalInterestForThisPeriod.interestPaymentDueToGrace()).plus(outstandingBalance) .plus(principalInterestForThisPeriod.interestPaymentDueToGrace()).plus(outstandingBalance)
.isGreaterThan(unprocessed)) { .isGreaterThan(unprocessed)) {
@@ -528,7 +546,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalCumulativePrincipal.minus(reducePrincipal), totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalCumulativePrincipal.minus(reducePrincipal), totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc, totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc,
mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), compoundingMap,
periodStartDateApplicableForInterest, scheduledDueDate, daysInPeriodApplicableForInterest); periodStartDateApplicableForInterest, scheduledDueDate, daysInPeriodApplicableForInterest, applicableVariations);
if (loanApplicationTerms.getFixedEmiAmount() != null if (loanApplicationTerms.getFixedEmiAmount() != null
&& loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) { && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) {
@@ -648,7 +666,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
periodNumber, mc, periodNumber, mc,
mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap), mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap),
compoundingMap, periodStartDateApplicableForInterest, calculateTill, compoundingMap, periodStartDateApplicableForInterest, calculateTill,
daysInPeriodApplicableForInterest); daysInPeriodApplicableForInterest, applicableVariations);
Money diff = interestForThisinstallment.minus(interestTillDate.interest()); Money diff = interestForThisinstallment.minus(interestTillDate.interest());
if (!outstandingBalance.minus(diff).isGreaterThanZero()) { if (!outstandingBalance.minus(diff).isGreaterThanZero()) {
outstandingBalance = outstandingBalance.minus(diff); outstandingBalance = outstandingBalance.minus(diff);
@@ -860,6 +878,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0); double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0);
int periodNumberTemp = 1; int periodNumberTemp = 1;
LocalDate lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); LocalDate lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
Collection<LoanTermVariationsData> applicableVariations = loanApplicationTerms.getLoanTermVariations();
do { do {
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, 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, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding,
loanApplicationTerms, periodNumberTemp, mc, loanApplicationTerms, periodNumberTemp, mc,
mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap, compoundingMap), compoundingMap, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap, compoundingMap), compoundingMap,
periodStartDate, detail.getTransactionDate(), daysInPeriod); periodStartDate, detail.getTransactionDate(), daysInPeriod, applicableVariations);
Money interest = principalInterestForThisPeriod.interest(); Money interest = principalInterestForThisPeriod.interest();
totalInterest = totalInterest.plus(interest); totalInterest = totalInterest.plus(interest);
@@ -922,7 +942,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms, totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding, loanApplicationTerms,
periodNumberTemp, mc, periodNumberTemp, mc,
mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap, compoundingMap), compoundingMap, mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap, compoundingMap), compoundingMap,
periodStartDate, actualRepaymentDate, daysInPeriod); periodStartDate, actualRepaymentDate, daysInPeriod, applicableVariations);
Money interest = principalInterestForThisPeriod.interest(); Money interest = principalInterestForThisPeriod.interest();
totalInterest = totalInterest.plus(interest); totalInterest = totalInterest.plus(interest);
if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) { if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
@@ -959,6 +979,18 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
return applicableTransactions; 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) { private List<LoanTransaction> createCurrentTransactionList(RecalculationDetail detail) {
List<LoanTransaction> currentTransactions = new ArrayList<>(2); List<LoanTransaction> currentTransactions = new ArrayList<>(2);
currentTransactions.add(detail.getTransaction()); currentTransactions.add(detail.getTransaction());
@@ -1340,7 +1372,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
@Override @Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, 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 Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary(); final LoanSummary loanSummary = loan.getSummary();
@@ -1482,7 +1515,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// get the loan application terms from the Loan object // get the loan application terms from the Loan object
final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance, 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 // update the number of repayments
loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);
@@ -1551,12 +1587,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// principal outstanding // principal outstanding
loanApplicationTerms.setPrincipal(totalPrincipalOutstanding); loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
// for applying variations
Collection<LoanTermVariationsData> applicableVariations = getApplicableTermVariationsForPeriod(installmentFromDate,
adjustedInstallmentDueDate, periodNumber, loanTermVariations);
// determine the interest & principal for the period // determine the interest & principal for the period
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction,
totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalCumulativePrincipal, totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext, totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms, periodNumber, mathContext,
null, null, installmentFromDate, adjustedInstallmentDueDate, daysInInstallment); null, null, installmentFromDate, adjustedInstallmentDueDate, daysInInstallment, applicableVariations);
// update the interest due for the period // update the interest due for the period
period.updateInterestDue(principalInterestForThisPeriod.interest()); period.updateInterestDue(principalInterestForThisPeriod.interest());
@@ -1575,7 +1615,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, totalCumulativeInterest,
totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, outstandingBalance,
loanApplicationTerms, periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate, loanApplicationTerms, periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate,
daysInInstallment); daysInInstallment, applicableVariations);
period.updatePrincipalDue(principalInterestForThisPeriod.principal()); period.updatePrincipalDue(principalInterestForThisPeriod.principal());
@@ -1642,7 +1682,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest, double interestCalculationGraceOnRepaymentPeriodFraction, Money totalCumulativePrincipal, Money totalCumulativeInterest,
Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance, Money totalInterestDueForLoan, Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance,
LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc, TreeMap<LocalDate, Money> principalVariation, 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) { protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) {
return periodNumber == numberOfRepayments; return periodNumber == numberOfRepayments;
@@ -1891,8 +1932,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true); periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true);
outstandingBalance = outstandingBalance.zero().plus(disburseAmt); outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
outstandingBalanceAsPerRest = outstandingBalance; outstandingBalanceAsPerRest = outstandingBalance;
principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt);
} }
// Block process the installment and creates the period if it falls // Block process the installment and creates the period if it falls
// before reschedule from date // before reschedule from date
// This will create the recalculation details by applying the // This will create the recalculation details by applying the
@@ -1914,7 +1956,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
periodNumber++; periodNumber++;
} }
for (Map.Entry<LocalDate, Money> disburseDetail : disburseDetailMap.entrySet()) { for (Map.Entry<LocalDate, Money> disburseDetail : disburseDetailMap.entrySet()) {
if (disburseDetail.getKey().isAfter(installment.getFromDate()) if (disburseDetail.getKey().isAfter(installment.getFromDate())
&& !disburseDetail.getKey().isAfter(installment.getDueDate())) { && !disburseDetail.getKey().isAfter(installment.getDueDate())) {
@@ -1926,6 +1968,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// updates actual outstanding balance with new // updates actual outstanding balance with new
// disbursement detail // disbursement detail
outstandingBalance = outstandingBalance.plus(disburseDetail.getValue()); outstandingBalance = outstandingBalance.plus(disburseDetail.getValue());
principalToBeScheduled = principalToBeScheduled.plus(disburseDetail.getValue());
} }
} }
@@ -2001,12 +2044,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// for partial schedule generation // for partial schedule generation
if (!newRepaymentScheduleInstallments.isEmpty()) { if (!newRepaymentScheduleInstallments.isEmpty()) {
loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO loanScheduleRecalculationDTO = LoanScheduleRecalculationDTO.createLoanScheduleDTOForPartialUpdate(periodNumber,
.createLoanScheduleDTOForPartialUpdate(periodNumber, instalmentNumber, periodStartDate, actualRepaymentDate, instalmentNumber, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, reducePrincipal, principalPortionMap,
totalRepaymentExpected, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, latePaymentMap, compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance,
disburseDetailMap, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, recalculationDetails,
recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate); loanRepaymentScheduleTransactionProcessor, scheduleTillDate);
retainedInstallments.addAll(newRepaymentScheduleInstallments); retainedInstallments.addAll(newRepaymentScheduleInstallments);
} }
@@ -5,7 +5,10 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
@@ -13,6 +16,7 @@ import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.service.DateUtils; import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
/** /**
@@ -49,7 +53,8 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
@SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, @SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace,
final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
final TreeMap<LocalDate, Money> principalVariation, final Map<LocalDate, Money> compoundingMap, 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; LocalDate interestStartDate = periodStartDate;
Money interestForThisInstallment = totalCumulativePrincipal.zero(); Money interestForThisInstallment = totalCumulativePrincipal.zero();
@@ -58,6 +63,20 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
Money balanceForInterestCalculation = outstandingBalance; Money balanceForInterestCalculation = outstandingBalance;
Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace;
final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays(); 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) { if (principalVariation != null) {
// identifies rest date after current date for reducing all // identifies rest date after current date for reducing all
// compounding // compounding
@@ -102,6 +121,9 @@ public class DecliningBalanceInterestLoanScheduleGenerator extends AbstractLoanS
compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee); compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee);
} }
balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()).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 { } else {
interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays(daysForInterestInFullPeriod, result interestForThisInstallment = interestForThisInstallment.plus(calculateInterestForDays(daysForInterestInFullPeriod, result
.interest().getAmount(), interestForDays)); .interest().getAmount(), interestForDays));
cumulatingInterestDueToGrace = cumulatingInterestDueToGrace.plus(calculateInterestForDays( cumulatingInterestDueToGrace = cumulatingInterestDueToGrace.plus(calculateInterestForDays(daysForInterestInFullPeriod, result
daysForInterestInFullPeriod, result.interestPaymentDueToGrace().minus(cumulatingInterestDueToGrace) .interestPaymentDueToGrace().minus(cumulatingInterestDueToGrace).getAmount(), interestForDays));
.getAmount(), interestForDays));
} }
Money interestForPeriod = interestForThisInstallment; Money interestForPeriod = interestForThisInstallment;
@@ -6,12 +6,14 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.math.MathContext; import java.math.MathContext;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import org.joda.time.Days; import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
public class FlatInterestLoanScheduleGenerator extends AbstractLoanScheduleGenerator { 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, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc,
@SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation, @SuppressWarnings("unused") TreeMap<LocalDate, Money> principalVariation,
@SuppressWarnings("unused") Map<LocalDate, Money> compoundingMap, LocalDate periodStartDate, LocalDate periodEndDate, @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(); final int daysInPeriodApplicableForInterest = Days.daysBetween(periodStartDate, periodEndDate).getDays();
Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator,
daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc, null); daysInPeriodApplicableForInterest, outstandingBalance, periodNumber, mc, null);
@@ -7,6 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
@@ -151,6 +152,8 @@ public final class LoanApplicationTerms {
private Money approvedPrincipal = null; private Money approvedPrincipal = null;
private final Collection<LoanTermVariationsData> loanTermVariations;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType, final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
@@ -168,7 +171,7 @@ public final class LoanApplicationTerms {
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
BigDecimal approvedAmount) { BigDecimal approvedAmount, Collection<LoanTermVariationsData> loanTermVariations) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
@@ -180,7 +183,7 @@ public final class LoanApplicationTerms {
disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance,
recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar, approvedAmount); installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
} }
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, 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 InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType, final RecalculationFrequencyType compoundingFrequencyType,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate,
Collection<LoanTermVariationsData> loanTermVariations) {
final Calendar loanCalendar = null; final Calendar loanCalendar = null;
return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek, return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek,
@@ -203,7 +207,8 @@ public final class LoanApplicationTerms {
loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations,
interestChargedFromDate, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, interestChargedFromDate, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType,
restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType,
loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar, approvedAmount); loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar, approvedAmount,
annualNominalInterestRate, loanTermVariations);
} }
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, 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 InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType, final RecalculationFrequencyType compoundingFrequencyType,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, 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 numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -227,7 +233,6 @@ public final class LoanApplicationTerms {
final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod();
final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod();
final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType();
final BigDecimal annualNominalInterestRate = loanProductRelatedDetail.getAnnualNominalInterestRate();
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail
.getInterestCalculationPeriodMethod(); .getInterestCalculationPeriodMethod();
final Money principalMoney = loanProductRelatedDetail.getPrincipal(); final Money principalMoney = loanProductRelatedDetail.getPrincipal();
@@ -250,7 +255,7 @@ public final class LoanApplicationTerms {
loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount); loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
} }
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, 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 recalculationFrequencyType, final CalendarInstance compoundingCalendarInstance,
final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment,
final Integer installmentAmountInMultiplesOf, 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 numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -272,7 +278,6 @@ public final class LoanApplicationTerms {
final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod();
final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod();
final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType();
final BigDecimal annualNominalInterestRate = loanProductRelatedDetail.getAnnualNominalInterestRate();
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail
.getInterestCalculationPeriodMethod(); .getInterestCalculationPeriodMethod();
final Money principalMoney = loanProductRelatedDetail.getPrincipal(); final Money principalMoney = loanProductRelatedDetail.getPrincipal();
@@ -302,28 +307,7 @@ public final class LoanApplicationTerms {
daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod,
interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount); loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations);
}
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);
} }
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -344,7 +328,7 @@ public final class LoanApplicationTerms {
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
BigDecimal approvedAmount) { BigDecimal approvedAmount, Collection<LoanTermVariationsData> loanTermVariations) {
this.currency = currency; this.currency = currency;
this.loanTermFrequency = loanTermFrequency; this.loanTermFrequency = loanTermFrequency;
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
@@ -394,6 +378,7 @@ public final class LoanApplicationTerms {
this.loanCalendar = loanCalendar; this.loanCalendar = loanCalendar;
this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount); this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount);
this.loanTermVariations = loanTermVariations;
} }
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, 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() { public BigDecimal getAnnualNominalInterestRate() {
return this.annualNominalInterestRate; return this.annualNominalInterestRate;
} }
@@ -1270,9 +1251,12 @@ public final class LoanApplicationTerms {
return loanCalendar; return loanCalendar;
} }
public BigDecimal getFixedPrincipalAmount() { public BigDecimal getFixedPrincipalAmount() {
return this.fixedPrincipalAmount; 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.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; 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.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -38,5 +39,5 @@ public interface LoanScheduleGenerator {
LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, 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.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
@@ -17,6 +18,7 @@ import org.apache.commons.lang.StringUtils;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService; import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper; 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.Holiday;
import org.mifosplatform.organisation.holiday.domain.HolidayRepository; import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
import org.mifosplatform.organisation.holiday.domain.HolidayStatusType; 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.DaysInMonthType;
import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.DaysInYearType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; 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.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
@@ -99,6 +104,7 @@ public class LoanScheduleAssembler {
private final ClientRepositoryWrapper clientRepository; private final ClientRepositoryWrapper clientRepository;
private final GroupRepositoryWrapper groupRepository; private final GroupRepositoryWrapper groupRepository;
private final WorkingDaysRepositoryWrapper workingDaysRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired @Autowired
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository, public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
@@ -107,7 +113,8 @@ public class LoanScheduleAssembler {
final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository, final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository,
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository, final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository) { final WorkingDaysRepositoryWrapper workingDaysRepository,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.fromApiJsonHelper = fromApiJsonHelper; this.fromApiJsonHelper = fromApiJsonHelper;
this.loanProductRepository = loanProductRepository; this.loanProductRepository = loanProductRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository; this.applicationCurrencyRepository = applicationCurrencyRepository;
@@ -120,6 +127,7 @@ public class LoanScheduleAssembler {
this.clientRepository = clientRepository; this.clientRepository = clientRepository;
this.groupRepository = groupRepository; this.groupRepository = groupRepository;
this.workingDaysRepository = workingDaysRepository; this.workingDaysRepository = workingDaysRepository;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
} }
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) { public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
@@ -164,8 +172,10 @@ public class LoanScheduleAssembler {
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element);
final PeriodFrequencyType interestRatePeriodFrequencyType = loanProduct.getInterestPeriodFrequencyType(); final PeriodFrequencyType interestRatePeriodFrequencyType = loanProduct.getInterestPeriodFrequencyType();
final BigDecimal annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, BigDecimal annualNominalInterestRate = BigDecimal.ZERO;
interestRatePerPeriod); if (interestRatePerPeriod != null) {
annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, interestRatePerPeriod);
}
// disbursement details // disbursement details
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element);
@@ -296,6 +306,31 @@ public class LoanScheduleAssembler {
final Integer installmentAmountInMultiplesOf = loanProduct.getInstallmentAmountInMultiplesOf(); 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, return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
@@ -304,7 +339,8 @@ public class LoanScheduleAssembler {
loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData,
graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType,
restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment,
installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO); installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO,
loanTermVariations);
} }
private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, 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.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; 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.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService; import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -63,6 +66,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
private final CalendarInstanceRepository calendarInstanceRepository; private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired @Autowired
public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer, public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer,
@@ -72,7 +76,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
final LoanAssembler loanAssembler, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository, final LoanAssembler loanAssembler, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final LoanAccountDomainService accountDomainService, final CalendarInstanceRepository calendarInstanceRepository, final LoanAccountDomainService accountDomainService, final CalendarInstanceRepository calendarInstanceRepository,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final ConfigurationDomainService configurationDomainService) { final ConfigurationDomainService configurationDomainService,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanScheduleAssembler = loanScheduleAssembler; this.loanScheduleAssembler = loanScheduleAssembler;
this.fromJsonHelper = fromJsonHelper; this.fromJsonHelper = fromJsonHelper;
@@ -86,6 +91,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
this.calendarInstanceRepository = calendarInstanceRepository; this.calendarInstanceRepository = calendarInstanceRepository;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.configurationDomainService = configurationDomainService; this.configurationDomainService = configurationDomainService;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
} }
@Override @Override
@@ -164,11 +170,12 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
LocalDate calculatedRepaymentsStartingFromDate = accountDomainService.getCalculatedRepaymentsStartingFromDate( LocalDate calculatedRepaymentsStartingFromDate = accountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance); loan.getDisbursementDate(), loan, calendarInstance);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency, LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance); calculatedRepaymentsStartingFromDate, restCalendarInstance, compoundingCalendarInstance, floatingRateDTO);
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount( LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = this.loanScheduleAssembler.calculatePrepaymentAmount(currency,
currency, today, loanApplicationTerms, loan.charges(), loan.getOfficeId(), loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), today, loanApplicationTerms, loan.charges(), loan.getOfficeId(),
loanRepaymentScheduleTransactionProcessor); loan.retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor);
Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus( Money totalAmount = totalPrincipal.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)).plus(
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
Money interestDue = Money.zero(currency); Money interestDue = Money.zero(currency);
@@ -209,4 +216,17 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
} }
loanScheduleData.updateFuturePeriods(futureInstallments); 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.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; 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.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator; 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, public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance, final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance,
final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar) { final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, final FloatingRateDTO floatingRateDTO) {
LoanRescheduleModel loanRescheduleModel = null; LoanRescheduleModel loanRescheduleModel = null;
switch (interestMethod) { switch (interestMethod) {
case DECLINING_BALANCE: case DECLINING_BALANCE:
loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar); applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
floatingRateDTO);
break; break;
case FLAT: case FLAT:
loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar); applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
floatingRateDTO);
break; break;
case INVALID: case INVALID:
@@ -10,6 +10,7 @@ import java.math.MathContext;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; 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.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
@@ -18,5 +19,5 @@ public interface LoanReschedulerFactory {
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, 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; package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service;
import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.List; 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.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; 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.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
@@ -47,6 +51,7 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
private final WorkingDaysRepositoryWrapper workingDaysRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService; private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final CalendarInstanceRepository calendarInstanceRepository; private final CalendarInstanceRepository calendarInstanceRepository;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired @Autowired
public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository, public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
@@ -54,7 +59,8 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository, final WorkingDaysRepositoryWrapper workingDaysRepository,
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService, final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
final CalendarInstanceRepository calendarInstanceRepository) { final CalendarInstanceRepository calendarInstanceRepository,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository; this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository; this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService; this.configurationDomainService = configurationDomainService;
@@ -62,6 +68,7 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
this.workingDaysRepository = workingDaysRepository; this.workingDaysRepository = workingDaysRepository;
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService; this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
this.calendarInstanceRepository = calendarInstanceRepository; this.calendarInstanceRepository = calendarInstanceRepository;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
} }
@Override @Override
@@ -73,8 +80,8 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
Loan loan = loanRescheduleRequest.getLoan(); Loan loan = loanRescheduleRequest.getLoan();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency(); final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
@@ -83,16 +90,16 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod(); final InterestMethod interestMethod = loan.getLoanRepaymentScheduleDetail().getInterestMethod();
final RoundingMode roundingMode = MoneyHelper.getRoundingMode(); final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mathContext = new MathContext(8, roundingMode); final MathContext mathContext = new MathContext(8, roundingMode);
List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive(
.createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
CalendarInstance compoundingCalendarInstance = null; CalendarInstance compoundingCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
} }
final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
@@ -100,12 +107,26 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
if (loanCalendarInstance != null) { if (loanCalendarInstance != null) {
loanCalendar = loanCalendarInstance.getCalendar(); loanCalendar = loanCalendarInstance.getCalendar();
} }
final FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
loanCalendar); loanCalendar, floatingRateDTO);
LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel, LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel,
oldPeriods); oldPeriods);
return loanRescheduleModelWithOldPeriods; 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.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; 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.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidByData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidByData;
import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO;
@@ -84,6 +87,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final LoanRepository loanRepository; private final LoanRepository loanRepository;
private final LoanAssembler loanAssembler; private final LoanAssembler loanAssembler;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
/** /**
* LoanRescheduleRequestWritePlatformServiceImpl constructor * LoanRescheduleRequestWritePlatformServiceImpl constructor
@@ -103,7 +107,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanScheduleGeneratorFactory loanScheduleFactory, final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanScheduleGeneratorFactory loanScheduleFactory,
final LoanTransactionRepository loanTransactionRepository, final LoanTransactionRepository loanTransactionRepository,
final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepository loanRepository, final JournalEntryWritePlatformService journalEntryWritePlatformService, final LoanRepository loanRepository,
final LoanAssembler loanAssembler) { final LoanAssembler loanAssembler, final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.loanRepositoryWrapper = loanRepositoryWrapper; this.loanRepositoryWrapper = loanRepositoryWrapper;
this.codeValueRepositoryWrapper = codeValueRepositoryWrapper; this.codeValueRepositoryWrapper = codeValueRepositoryWrapper;
this.platformSecurityContext = platformSecurityContext; this.platformSecurityContext = platformSecurityContext;
@@ -123,6 +127,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
} }
/** /**
@@ -272,8 +277,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
LocalDate approvedOnDate = jsonCommand.localDateValueOfParameterNamed("approvedOnDate"); LocalDate approvedOnDate = jsonCommand.localDateValueOfParameterNamed("approvedOnDate");
final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()) final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(
.withLocale(jsonCommand.extractLocale()); jsonCommand.extractLocale());
changes.put("locale", jsonCommand.locale()); changes.put("locale", jsonCommand.locale());
changes.put("dateFormat", jsonCommand.dateFormat()); changes.put("dateFormat", jsonCommand.dateFormat());
@@ -285,8 +290,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final LoanSummary loanSummary = loan.getSummary(); final LoanSummary loanSummary = loan.getSummary();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
loan.getDisbursementDate().toDate()); .getDisbursementDate().toDate());
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail(); final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency(); final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();
@@ -314,9 +319,10 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
if (loanCalendarInstance != null) { if (loanCalendarInstance != null) {
loanCalendar = loanCalendarInstance.getCalendar(); loanCalendar = loanCalendarInstance.getCalendar();
} }
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
loanCalendar); loanCalendar, floatingRateDTO);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods(); final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
@@ -373,7 +379,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
waiveLoanCharges(loan, waiveLoanCharges); waiveLoanCharges(loan, waiveLoanCharges);
// update the Loan summary // 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 // update the total number of schedule repayments
loan.updateNumberOfRepayments(periods.size()); loan.updateNumberOfRepayments(periods.size());
@@ -458,26 +465,27 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = this.loanAccountDomainService calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
.getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, calendarInstance); loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate()
loan.getDisbursementDate().toDate()); .toDate());
workingDays = this.workingDaysRepository.findOne(); workingDays = this.workingDaysRepository.findOne();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
recalculateFrom = DateUtils.getLocalDateOfTenant(); recalculateFrom = DateUtils.getLocalDateOfTenant();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
recalculateFrom, overdurPenaltyWaitPeriod); recalculateFrom, overdurPenaltyWaitPeriod, floatingRateDTO);
Money accruedCharge = Money.zero(loan.getCurrency()); Money accruedCharge = Money.zero(loan.getCurrency());
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy(
.retriveLoanChargesPaidBy(loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber); loanCharge.getId(), LoanTransactionType.ACCRUAL, loanInstallmentNumber);
for (LoanChargePaidByData chargePaidByData : chargePaidByDatas) { for (LoanChargePaidByData chargePaidByData : chargePaidByDatas) {
accruedCharge = accruedCharge.plus(chargePaidByData.getAmount()); accruedCharge = accruedCharge.plus(chargePaidByData.getAmount());
} }
@@ -527,8 +535,8 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
LocalDate rejectedOnDate = jsonCommand.localDateValueOfParameterNamed("rejectedOnDate"); LocalDate rejectedOnDate = jsonCommand.localDateValueOfParameterNamed("rejectedOnDate");
final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()) final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern(jsonCommand.dateFormat()).withLocale(
.withLocale(jsonCommand.extractLocale()); jsonCommand.extractLocale());
changes.put("locale", jsonCommand.locale()); changes.put("locale", jsonCommand.locale());
changes.put("dateFormat", jsonCommand.dateFormat()); 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", throw new PlatformDataIntegrityException("error.msg.loan.reschedule.unknown.data.integrity.issue",
"Unknown data integrity issue with resource."); "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", final Set<String> supportedParameters = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanType", "calendarId", "productId",
"accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId", "principal", "accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId", "principal",
"inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency",
"loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", "interestRateFrequencyType", "loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType",
"expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment", "graceOnInterestPayment", "interestRateFrequencyType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment",
"graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", "charges", "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale",
"collateral", "syncDisbursementWithMeeting", "linkAccountId", LoanApiConstants.disbursementDataParameterName, "dateFormat", "charges", "collateral", "syncDisbursementWithMeeting", "linkAccountId",
LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName,
LoanProductConstants.graceOnArrearsAgeingParameterName, LoanProductConstants.recalculationRestFrequencyDateParamName,"createStandingInstructionAtDisbursement")); LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName,
LoanProductConstants.recalculationRestFrequencyDateParamName, "createStandingInstructionAtDisbursement",
LoanApiConstants.isFloatingInterestRateParameterName, LoanApiConstants.interestRateDifferentialParameterName));
private final FromJsonHelper fromApiJsonHelper; 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", final Set<String> supportedParameters = new HashSet<>(Arrays.asList("dateFormat", "locale", "id", "clientId", "groupId", "loanType",
"productId", "principal", "loanTermFrequency", "loanTermFrequencyType", "numberOfRepayments", "repaymentEvery", "productId", "principal", "loanTermFrequency", "loanTermFrequencyType", "numberOfRepayments", "repaymentEvery",
"repaymentFrequencyType", "repaymentFrequencyNthDayType", "repaymentFrequencyDayOfWeekType", "repaymentFrequencyType", "repaymentFrequencyNthDayType", "repaymentFrequencyDayOfWeekType", "interestRatePerPeriod",
"interestRatePerPeriod", "amortizationType", "interestType",
"amortizationType", LoanApiConstants.isFloatingInterestRate,
"interestType", LoanApiConstants.interestRateDifferential,
"interestCalculationPeriodType", "interestCalculationPeriodType",
"interestRateFrequencyType", "interestRateFrequencyType",
"expectedDisbursementDate", "expectedDisbursementDate",
@@ -203,12 +203,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull() baseDataValidator.reset().parameter(repaymentEveryFrequencyTypeParameterName).value(repaymentEveryType).notNull()
.inMinMaxRange(0, 3); .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 String interestTypeParameterName = "interestType";
final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element); final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestTypeParameterName, element);
baseDataValidator.reset().parameter(interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1); 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() baseDataValidator.reset().parameter(interestCalculationPeriodTypeParameterName).value(interestCalculationPeriodType).notNull()
.inMinMaxRange(0, 1); .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 String amortizationTypeParameterName = "amortizationType";
final Integer amortizationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(amortizationTypeParameterName, element); final Integer amortizationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(amortizationTypeParameterName, element);
baseDataValidator.reset().parameter(amortizationTypeParameterName).value(amortizationType).notNull().inMinMaxRange(0, 1); 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); } 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(); } if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); }
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType(); 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); 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"; final String interestTypeParameterName = "interestType";
Integer interestType = null;
if (this.fromApiJsonHelper.parameterExists(interestTypeParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(interestTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; 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); 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"; final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType";
if (this.fromApiJsonHelper.parameterExists(interestCalculationPeriodTypeParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(interestCalculationPeriodTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
@@ -849,23 +979,23 @@ public final class LoanApplicationCommandFromApiJsonHelper {
if (variationArray != null) { if (variationArray != null) {
for (int i = 0; i < variationArray.size(); i++) { for (int i = 0; i < variationArray.size(); i++) {
final JsonObject jsonObject1 = variationArray.get(i).getAsJsonObject(); final JsonObject jsonObject1 = variationArray.get(i).getAsJsonObject();
if(jsonObject1.has(LoanApiConstants.disbursementDateParameterName)){ if (jsonObject1.has(LoanApiConstants.disbursementDateParameterName)) {
LocalDate date1 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, jsonObject1, LocalDate date1 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName,
dateFormat, locale); jsonObject1, dateFormat, locale);
for (int j = i + 1; j < variationArray.size(); j++) { for (int j = i + 1; j < variationArray.size(); j++) {
final JsonObject jsonObject2 = variationArray.get(j).getAsJsonObject(); final JsonObject jsonObject2 = variationArray.get(j).getAsJsonObject();
if(jsonObject2.has(LoanApiConstants.disbursementDateParameterName)){ if (jsonObject2.has(LoanApiConstants.disbursementDateParameterName)) {
LocalDate date2 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName, LocalDate date2 = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.disbursementDateParameterName,
jsonObject2, dateFormat, locale); jsonObject2, dateFormat, locale);
if (date1.isAfter(date2)) { if (date1.isAfter(date2)) {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName) baseDataValidator.reset().parameter(LoanApiConstants.disbursementDataParameterName)
.failWithCode(LoanApiConstants.DISBURSEMENT_DATES_NOT_IN_ORDER); .failWithCode(LoanApiConstants.DISBURSEMENT_DATES_NOT_IN_ORDER);
} }
} }
} }
} }
} }
} }
} }
@@ -918,8 +1048,9 @@ public final class LoanApplicationCommandFromApiJsonHelper {
if (expectedDisbursement.equals(expectedDisbursementDate)) { if (expectedDisbursement.equals(expectedDisbursementDate)) {
isFirstinstallmentOnExpectedDisbursementDate = true; isFirstinstallmentOnExpectedDisbursementDate = true;
} }
}i++; }
i++;
} while (i < variationArray.size()); } while (i < variationArray.size());
if (!isFirstinstallmentOnExpectedDisbursementDate) { if (!isFirstinstallmentOnExpectedDisbursementDate) {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName) baseDataValidator.reset().parameter(LoanApiConstants.disbursementDateParameterName)
@@ -472,13 +472,23 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
try { try {
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
final Loan existingLoanApplication = retrieveLoanBy(loanId); final Loan existingLoanApplication = retrieveLoanBy(loanId);
this.fromApiJsonDeserializer.validateForModify(command.json(), existingLoanApplication.loanProduct());
checkClientOrGroupActive(existingLoanApplication);
if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified( if (!existingLoanApplication.isSubmittedAndPendingApproval()) { throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(
loanId); } 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(); final Set<LoanCharge> existingCharges = existingLoanApplication.charges();
Map<Long, LoanChargeData> chargesMap = new HashMap<>(); Map<Long, LoanChargeData> chargesMap = new HashMap<>();
for (LoanCharge charge : existingCharges) { for (LoanCharge charge : existingCharges) {
@@ -554,33 +564,34 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
existingLoanApplication.updateGroup(group); existingLoanApplication.updateGroup(group);
} }
final String productIdParamName = "productId"; if (newLoanProduct != null) {
if (changes.containsKey(productIdParamName)) { existingLoanApplication.updateLoanProduct(newLoanProduct);
final Long productId = command.longValueOfParameterNamed(productIdParamName);
final LoanProduct loanProduct = this.loanProductRepository.findOne(productId);
if (loanProduct == null) { throw new LoanProductNotFoundException(productId); }
existingLoanApplication.updateLoanProduct(loanProduct);
if (!changes.containsKey("interestRateFrequencyType")) { if (!changes.containsKey("interestRateFrequencyType")) {
existingLoanApplication.updateInterestRateFrequencyType(); existingLoanApplication.updateInterestRateFrequencyType();
} }
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); 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 clientId = this.fromJsonHelper.extractLongNamed("clientId", command.parsedJson());
final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", command.parsedJson()); final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", command.parsedJson());
Integer cycleNumber = 0; Integer cycleNumber = 0;
if (clientId != null) { if (clientId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, loanProduct.getId()); cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, newLoanProduct.getId());
} else if (groupId != null) { } else if (groupId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(), cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(),
loanProduct.getId()); newLoanProduct.getId());
} }
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator,
loanProduct, cycleNumber); newLoanProduct, cycleNumber);
} else { } else {
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(command.parsedJson(), baseDataValidator, 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); } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); }
} }
@@ -215,6 +215,9 @@ public class LoanAssembler {
Group group = null; Group group = null;
final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element); 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 loanTypeParameterName = "loanType";
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element); final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
@@ -239,20 +242,21 @@ public class LoanAssembler {
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(), loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(),
loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement); createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential);
} else if (group != null) { } else if (group != null) {
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement); createStandingInstructionAtDisbursement,isFloatingInterestRate, interestRateDifferential);
} else if (client != null) { } else if (client != null) {
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund, loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund,
loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, 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.infrastructure.core.service.SearchParameters;
import org.mifosplatform.organisation.staff.data.StaffData; import org.mifosplatform.organisation.staff.data.StaffData;
import org.mifosplatform.portfolio.calendar.data.CalendarData; 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.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData; import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData;
import org.mifosplatform.portfolio.loanaccount.data.LoanApprovalData; import org.mifosplatform.portfolio.loanaccount.data.LoanApprovalData;
@@ -95,7 +96,7 @@ public interface LoanReadPlatformService {
Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate); Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate);
Collection<Long> fetchArrearLoans(); Collection<Long> fetchLoansForInterestRecalculation();
LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate onDate); LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId, LocalDate onDate);
@@ -110,4 +111,6 @@ public interface LoanReadPlatformService {
PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId); PaidInAdvanceData retrieveTotalPaidInAdvance(Long loanId);
LoanTransactionData retrieveRefundByCashTemplate(Long loanId); LoanTransactionData retrieveRefundByCashTemplate(Long loanId);
Collection<InterestRatePeriodData> retrieveLoanInterestRatePeriodData(Long loanId);
} }
@@ -5,6 +5,8 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.service; package org.mifosplatform.portfolio.loanaccount.service;
import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestType;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; 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.client.service.ClientReadPlatformService;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.CommonEnumerations; 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.data.FundData;
import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
import org.mifosplatform.portfolio.group.data.GroupGeneralData; 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.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData; 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.LoanDropdownReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations;
import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService;
@@ -142,6 +149,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final WorkingDaysRepositoryWrapper workingDaysRepository; private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory; private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired @Autowired
public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository, public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository,
@@ -155,7 +163,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository, final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository,
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
final WorkingDaysRepositoryWrapper workingDaysRepository, PaymentTypeReadPlatformService paymentTypeReadPlatformService, final WorkingDaysRepositoryWrapper workingDaysRepository, PaymentTypeReadPlatformService paymentTypeReadPlatformService,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory) { final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.context = context; this.context = context;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
@@ -178,6 +187,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.workingDaysRepository = workingDaysRepository; this.workingDaysRepository = workingDaysRepository;
this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory; this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
} }
@Override @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(" 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 ?)"); sqlBuilder.append(" where l.id=? and ( o.hierarchy like ? or transferToOffice.hierarchy like ?)");
return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), rm, return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), rm, new Object[] { loanId, hierarchySearchString,
new Object[] { loanId, hierarchySearchString, hierarchySearchString }); hierarchySearchString });
} catch (final EmptyResultDataAccessException e) { } catch (final EmptyResultDataAccessException e) {
throw new LoanNotFoundException(loanId); throw new LoanNotFoundException(loanId);
} }
@@ -236,7 +246,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
/*** /***
* TODO Vishwas: Remove references to "Contra" from the codebase * 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 "; + " 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 }); return this.jdbcTemplate.query(sql, rm, new Object[] { loanId });
} catch (final EmptyResultDataAccessException e) { } catch (final EmptyResultDataAccessException e) {
@@ -395,10 +406,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final BigDecimal outstandingLoanBalance = null; final BigDecimal outstandingLoanBalance = null;
final BigDecimal unrecognizedIncomePortion = null; final BigDecimal unrecognizedIncomePortion = null;
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment
loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(), .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency)
loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(), .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion, loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion,
paymentOptions, null, null, null, outstandingLoanBalance, false); paymentOptions, null, null, null, outstandingLoanBalance, false);
} }
@@ -425,26 +435,26 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
loan.getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); .getDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
} }
final FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(this.loanScheduleFactory, final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchPrepaymentDetail(this.loanScheduleFactory,
restCalendarInstance, compoundingCalendarInstance, holidayDetailDTO, onDate); restCalendarInstance, compoundingCalendarInstance, holidayDetailDTO, onDate, floatingRateDTO);
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT); final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(); final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount();
final BigDecimal unrecognizedIncomePortion = null; final BigDecimal unrecognizedIncomePortion = null;
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getTotalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment
loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount(), .getPrincipalOutstanding(currency).getAmount(), loanRepaymentScheduleInstallment.getInterestOutstanding(currency)
loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(), .getAmount(), loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion, loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion,
paymentOptions, null, null, null, outstandingLoanBalance, false); 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," 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," + " 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.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.allow_multiple_disbursals as multiDisburseLoan,"
+ " lp.can_define_fixed_emi_amount as canDefineInstallmentAmount," + " lp.can_define_fixed_emi_amount as canDefineInstallmentAmount,"
+ " c.id as clientId, c.display_name as clientName, c.office_id as clientOfficeId," + " 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, " + " 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.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.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_writtenoff_derived as principalWrittenOff,"
+ " l.principal_outstanding_derived as principalOutstanding," + " l.interest_charged_derived as interestCharged," + " l.principal_outstanding_derived as principalOutstanding,"
+ " l.interest_repaid_derived as interestPaid," + " l.interest_waived_derived as interestWaived," + " l.interest_charged_derived as interestCharged,"
+ " l.interest_writtenoff_derived as interestWrittenOff," + " l.interest_outstanding_derived as interestOutstanding," + " 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.fee_charges_charged_derived as feeChargesCharged,"
+ " l.total_charges_due_at_disbursement_derived as feeChargesDueAtDisbursementCharged," + " 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_writtenoff_derived as feeChargesWrittenOff,"
+ " l.fee_charges_outstanding_derived as feeChargesOutstanding," + " l.fee_charges_outstanding_derived as feeChargesOutstanding,"
+ " l.penalty_charges_charged_derived as penaltyChargesCharged," + " 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_waived_derived as penaltyChargesWaived,"
+ " l.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff," + " l.penalty_charges_writtenoff_derived as penaltyChargesWrittenOff,"
+ " l.penalty_charges_outstanding_derived as penaltyChargesOutstanding," + " l.penalty_charges_outstanding_derived as penaltyChargesOutstanding,"
+ " l.total_expected_repayment_derived as totalExpectedRepayment," + " l.total_repayment_derived as totalRepayment," + " l.total_expected_repayment_derived as totalExpectedRepayment,"
+ " l.total_expected_costofloan_derived as totalExpectedCostOfLoan," + " l.total_costofloan_derived as totalCostOfLoan," + " l.total_repayment_derived as totalRepayment,"
+ " l.total_waived_derived as totalWaived," + " l.total_writtenoff_derived as totalWrittenOff," + " l.total_expected_costofloan_derived as totalExpectedCostOfLoan,"
+ " l.total_outstanding_derived as totalOutstanding," + " l.total_overpaid_derived as totalOverpaid," + " l.total_costofloan_derived as totalCostOfLoan,"
+ " l.fixed_emi_amount as fixedEmiAmount," + " l.max_outstanding_loan_balance as outstandingLoanBalance," + " l.total_waived_derived as totalWaived,"
+ " la.principal_overdue_derived as principalOverdue," + " la.interest_overdue_derived as interestOverdue," + " 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.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," + " la.overdue_since_date_derived as overdueSinceDate,"
+ " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting," + " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting,"
+ " l.loan_counter as loanCounter, l.loan_product_counter as loanProductCounter," + " 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.rest_freqency_date as restFrequencyDate, "
+ " lir.compounding_frequency_type_enum as compoundingFrequencyEnum, lir.compounding_frequency_interval as compoundingInterval, " + " lir.compounding_frequency_type_enum as compoundingFrequencyEnum, lir.compounding_frequency_interval as compoundingInterval, "
+ " lir.compounding_freqency_date as compoundingFrequencyDate, " + " 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" // + " join m_product_loan lp on lp.id = l.product_id" //
+ " left join m_loan_recalculation_details lir on lir.loan_id = l.id " + " left join m_loan_recalculation_details lir on lir.loan_id = l.id "
+ " join m_currency rc on rc.`code` = l.currency_code" // + " 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 rbu on rbu.id = l.rejectedon_userid"
+ " left join m_appuser wbu on wbu.id = l.withdrawnon_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 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 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"; + " 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 Long loanProductId = JdbcSupport.getLong(rs, "loanProductId");
final String loanProductName = rs.getString("loanProductName"); final String loanProductName = rs.getString("loanProductName");
final String loanProductDescription = rs.getString("loanProductDescription"); final String loanProductDescription = rs.getString("loanProductDescription");
final boolean isLoanProductLinkedToFloatingRate = rs.getBoolean("isLoanProductLinkedToFloatingRate");
final Boolean multiDisburseLoan = rs.getBoolean("multiDisburseLoan"); final Boolean multiDisburseLoan = rs.getBoolean("multiDisburseLoan");
final Boolean canDefineInstallmentAmount = rs.getBoolean("canDefineInstallmentAmount"); final Boolean canDefineInstallmentAmount = rs.getBoolean("canDefineInstallmentAmount");
final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance"); final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance");
@@ -720,6 +748,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaymentEvery"); final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaymentEvery");
final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod"); final BigDecimal interestRatePerPeriod = rs.getBigDecimal("interestRatePerPeriod");
final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); 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 graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment");
final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment"); 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, 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, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, approvedPrincipal, totalOverpaid,
inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType, inArrearsTolerance, termFrequency, termPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentFrequencyType,
repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName,
amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType,
interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, expectedFirstRepaymentOnDate,
graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline,
syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter,
fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing,
isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement); 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.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.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, " + " 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 " + " 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_product_loan_charge plc on plc.product_loan_id = ml.product_id "
+ " join m_charge mc on mc.id = plc.charge_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()); this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(data.amount());
} else if (data.isDueForDisbursement(fromDate, dueDate)) { } else if (data.isDueForDisbursement(fromDate, dueDate)) {
if (!excludePastUndisbursed || (excludePastUndisbursed if (!excludePastUndisbursed
&& (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) { || (excludePastUndisbursed && (data.isDisbursed() || !data.disbursementDate().isBefore(LocalDate.now())))) {
principal = principal.add(data.amount()); principal = principal.add(data.amount());
if (data.getChargeAmount() == null) { if (data.getChargeAmount() == null) {
final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod( final LoanSchedulePeriodData periodData = LoanSchedulePeriodData.disbursementOnlyPeriod(
@@ -1093,12 +1125,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
"totalPaidInAdvanceForPeriod"); "totalPaidInAdvanceForPeriod");
final BigDecimal totalPaidLateForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalPaidLateForPeriod"); final BigDecimal totalPaidLateForPeriod = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalPaidLateForPeriod");
final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived) final BigDecimal penaltyChargesActualDue = penaltyChargesExpectedDue.subtract(penaltyChargesWaived).subtract(
.subtract(penaltyChargesWrittenOff); penaltyChargesWrittenOff);
final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid); final BigDecimal penaltyChargesOutstanding = penaltyChargesActualDue.subtract(penaltyChargesPaid);
final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue) final BigDecimal totalExpectedCostOfLoanForPeriod = interestExpectedDue.add(feeChargesExpectedDue).add(
.add(penaltyChargesExpectedDue); penaltyChargesExpectedDue);
final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod); final BigDecimal totalDueForPeriod = principalDue.add(totalExpectedCostOfLoanForPeriod);
final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid).add(penaltyChargesPaid); final BigDecimal totalPaidForPeriod = principalPaid.add(interestPaid).add(feeChargesPaid).add(penaltyChargesPaid);
@@ -1139,11 +1171,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
periods.add(periodData); periods.add(periodData);
} }
return new LoanScheduleData(this.currency, periods, loanTermInDays, totalPrincipalDisbursed, totalPrincipalExpected.getAmount(), return new LoanScheduleData(this.currency, periods, loanTermInDays, totalPrincipalDisbursed,
totalPrincipalPaid.getAmount(), totalInterestCharged.getAmount(), totalFeeChargesCharged.getAmount(), totalPrincipalExpected.getAmount(), totalPrincipalPaid.getAmount(), totalInterestCharged.getAmount(),
totalPenaltyChargesCharged.getAmount(), totalWaived.getAmount(), totalWrittenOff.getAmount(), totalFeeChargesCharged.getAmount(), totalPenaltyChargesCharged.getAmount(), totalWaived.getAmount(),
totalRepaymentExpected.getAmount(), totalRepayment.getAmount(), totalPaidInAdvance.getAmount(), totalWrittenOff.getAmount(), totalRepaymentExpected.getAmount(), totalRepayment.getAmount(),
totalPaidLate.getAmount(), totalOutstanding.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.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.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.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, " + " tr.manually_adjusted_or_reversed as manuallyReversed, "
+ " pd.payment_type_id as paymentType,pd.account_number as accountNumber,pd.check_number as checkNumber, " + " 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, " + " 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," + " fromtran.description as fromTransferDescription,"
+ " totran.id as toTransferId, totran.is_reversed as toTransferReversed," + " totran.id as toTransferId, totran.is_reversed as toTransferReversed,"
+ " totran.transaction_date as toTransferDate, totran.amount as toTransferAmount," + " 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 " + " 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 fromtran on fromtran.from_loan_transaction_id = tr.id "
+ " left join m_account_transfer_transaction totran on totran.to_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(); .retrieveInterestRateFrequencyTypeOptions();
final Collection<EnumOptionData> amortizationTypeOptions = this.loanDropdownReadPlatformService final Collection<EnumOptionData> amortizationTypeOptions = this.loanDropdownReadPlatformService
.retrieveLoanAmortizationTypeOptions(); .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 final Collection<EnumOptionData> interestCalculationPeriodTypeOptions = this.loanDropdownReadPlatformService
.retrieveLoanInterestRateCalculatedInPeriodOptions(); .retrieveLoanInterestRateCalculatedInPeriodOptions();
final Collection<FundData> fundOptions = this.fundReadPlatformService.retrieveAllFunds(); final Collection<FundData> fundOptions = this.fundReadPlatformService.retrieveAllFunds();
@@ -1334,8 +1373,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId,
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT }); new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT });
} else { } else {
chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, chargeOptions = this.chargeReadPlatformService.retrieveLoanProductApplicableCharges(productId, new ChargeTimeType[] {
new ChargeTimeType[] { ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT }); ChargeTimeType.OVERDUE_INSTALLMENT, ChargeTimeType.TRANCHE_DISBURSEMENT });
} }
Integer loanCycleCounter = null; Integer loanCycleCounter = null;
@@ -1395,8 +1434,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override @Override
public Collection<CalendarData> retrieveCalendars(final Long groupId) { public Collection<CalendarData> retrieveCalendars(final Long groupId) {
Collection<CalendarData> calendarsData = new ArrayList<>(); Collection<CalendarData> calendarsData = new ArrayList<>();
calendarsData.addAll( calendarsData.addAll(this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId,
this.calendarReadPlatformService.retrieveParentCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null)); CalendarEntityType.GROUPS.getValue(), null));
calendarsData calendarsData
.addAll(this.calendarReadPlatformService.retrieveCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null)); .addAll(this.calendarReadPlatformService.retrieveCalendarsByEntity(groupId, CalendarEntityType.GROUPS.getValue(), null));
calendarsData = this.calendarReadPlatformService.updateWithRecurringDates(calendarsData); calendarsData = this.calendarReadPlatformService.updateWithRecurringDates(calendarsData);
@@ -1518,13 +1557,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
LoanScheduleAccrualMapper mapper = new LoanScheduleAccrualMapper(); LoanScheduleAccrualMapper mapper = new LoanScheduleAccrualMapper();
final StringBuilder sqlBuilder = new StringBuilder(400); 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(" 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.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(" 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"); .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, return this.jdbcTemplate.query(sqlBuilder.toString(), mapper, new Object[] { LoanStatus.ACTIVE.getValue(),
new Object[] { LoanStatus.ACTIVE.getValue(), AccountingRuleType.ACCRUAL_PERIODIC.getValue() }); AccountingRuleType.ACCRUAL_PERIODIC.getValue() });
} }
@Override @Override
@@ -1532,7 +1573,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
LoanSchedulePeriodicAccrualMapper mapper = new LoanSchedulePeriodicAccrualMapper(); LoanSchedulePeriodicAccrualMapper mapper = new LoanSchedulePeriodicAccrualMapper();
final StringBuilder sqlBuilder = new StringBuilder(400); 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(" 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.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(" 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() { public String schema() {
final StringBuilder sqlBuilder = new StringBuilder(400); 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.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.installment as installmentNumber, ")
.append("ls.duedate as duedate,ls.fromdate as fromdate ,ls.id as scheduleId,loan.product_id as productId,") .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.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("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(" 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") .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() { public String schema() {
final StringBuilder sqlBuilder = new StringBuilder(400); 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.duedate as duedate,ls.fromdate as fromdate,ls.id as scheduleId,loan.product_id as productId,")
.append("ls.installment as installmentNumber, ") .append("ls.installment as installmentNumber, ")
.append("ls.interest_amount as interest, ls.interest_waived_derived as interestWaived,") .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("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(" 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") .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 @Override
public Collection<Long> fetchArrearLoans() { public Collection<Long> fetchLoansForInterestRecalculation() {
StringBuilder sqlBuilder = new StringBuilder(); StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("SELECT ml.id FROM m_loan ml "); 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(" 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 "); 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(" WHERE ml.loan_status_id = ? ");
sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 ");
sqlBuilder.append(" and ml.is_npa = 0 "); 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 (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ?)");
sqlBuilder.append(" and (("); sqlBuilder.append(" and ((");
sqlBuilder.append(" mr.completed_derived is false "); sqlBuilder.append(" mr.completed_derived is false ");
sqlBuilder.append(" and mr.duedate < ? )"); 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"); sqlBuilder.append(" group by ml.id");
try { try {
String currentdate = formatter.print(DateUtils.getLocalDateOfTenant()); String currentdate = formatter.print(DateUtils.getLocalDateOfTenant());
return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, // will look only for yesterday modified rates
new Object[] { LoanStatus.ACTIVE.getValue(), currentdate, currentdate, currentdate }); 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) { } catch (final EmptyResultDataAccessException e) {
return null; return null;
} }
@@ -1835,10 +1901,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete, return LoanSchedulePeriodData.repaymentPeriodWithPayments(loanId, period, fromDate, dueDate, obligationsMetOnDate, complete,
principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan, principalOriginalDue, principalPaid, principalWrittenOff, principalOutstanding, outstandingPrincipalBalanceOfLoan,
interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding, feeChargesDue, interestDueOnPrincipalOutstanding, interestPaid, interestWaived, interestWrittenOff, interestOutstanding,
feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue, penaltyChargesPaid, feeChargesDue, feeChargesPaid, feeChargesWaived, feeChargesWrittenOff, feeChargesOutstanding, penaltyChargesDue,
penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod, totalPaid, penaltyChargesPaid, penaltyChargesWaived, penaltyChargesWrittenOff, penaltyChargesOutstanding, totalDueForPeriod,
totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding, totalPaid, totalPaidInAdvanceForPeriod, totalPaidLateForPeriod, totalWaived, totalWrittenOff, totalOutstanding,
totalActualCostOfLoanForPeriod); totalActualCostOfLoanForPeriod);
} }
} }
@@ -1847,8 +1913,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
public Date retrieveMinimumDateOfRepaymentTransaction(Long loanId) { public Date retrieveMinimumDateOfRepaymentTransaction(Long loanId) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
Date date = this.jdbcTemplate.queryForObject( Date date = this.jdbcTemplate.queryForObject(
"select min(transaction_date) from m_loan_transaction where loan_id=? and transaction_type_enum=2", new Object[] { loanId }, "select min(transaction_date) from m_loan_transaction where loan_id=? and transaction_type_enum=2",
Date.class); new Object[] { loanId }, Date.class);
return date; return date;
} }
@@ -1894,8 +1960,66 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); final Collection<PaymentTypeData> paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate, return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null, null, retrieveTotalPaidInAdvance(loan.getId()).getPaidInAdvance(), null, null, null, null, null, null, paymentOptions, null,
null, null, false); 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.annotation.CronTarget;
import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException; import org.mifosplatform.infrastructure.jobs.exception.JobExecutionException;
import org.mifosplatform.infrastructure.jobs.service.JobName; import org.mifosplatform.infrastructure.jobs.service.JobName;
import org.mifosplatform.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -31,13 +32,16 @@ public class LoanSchedularServiceImpl implements LoanSchedularService {
private final ConfigurationDomainService configurationDomainService; private final ConfigurationDomainService configurationDomainService;
private final LoanReadPlatformService loanReadPlatformService; private final LoanReadPlatformService loanReadPlatformService;
private final LoanWritePlatformService loanWritePlatformService; private final LoanWritePlatformService loanWritePlatformService;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired @Autowired
public LoanSchedularServiceImpl(final ConfigurationDomainService configurationDomainService, public LoanSchedularServiceImpl(final ConfigurationDomainService configurationDomainService,
final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService) { final LoanReadPlatformService loanReadPlatformService, final LoanWritePlatformService loanWritePlatformService,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.configurationDomainService = configurationDomainService; this.configurationDomainService = configurationDomainService;
this.loanReadPlatformService = loanReadPlatformService; this.loanReadPlatformService = loanReadPlatformService;
this.loanWritePlatformService = loanWritePlatformService; this.loanWritePlatformService = loanWritePlatformService;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
} }
@Override @Override
@@ -92,11 +96,11 @@ public class LoanSchedularServiceImpl implements LoanSchedularService {
if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
} }
} }
@Override @Override
@CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN) @CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN)
public void recalculateInterest() { public void recalculateInterest() {
Collection<Long> loanIds = this.loanReadPlatformService.fetchArrearLoans(); Collection<Long> loanIds = this.loanReadPlatformService.fetchLoansForInterestRecalculation();
for (Long loanId : loanIds) { for (Long loanId : loanIds) {
this.loanWritePlatformService.recalculateInterest(loanId); 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.BusinessEventNotificationConstants.BUSINESS_EVENTS;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.BusinessEventNotifierService; 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.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
@@ -206,6 +209,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final AccountTransferDetailRepository accountTransferDetailRepository; private final AccountTransferDetailRepository accountTransferDetailRepository;
private final BusinessEventNotifierService businessEventNotifierService; private final BusinessEventNotifierService businessEventNotifierService;
private final GuarantorDomainService guarantorDomainService; private final GuarantorDomainService guarantorDomainService;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
@Autowired @Autowired
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -231,7 +235,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper, final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper,
final AccountAssociationsRepository accountAssociationRepository, final AccountAssociationsRepository accountAssociationRepository,
final AccountTransferDetailRepository accountTransferDetailRepository, final AccountTransferDetailRepository accountTransferDetailRepository,
final BusinessEventNotifierService businessEventNotifierService, final GuarantorDomainService guarantorDomainService) { final BusinessEventNotifierService businessEventNotifierService, final GuarantorDomainService guarantorDomainService,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
this.context = context; this.context = context;
this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanEventApiJsonValidator = loanEventApiJsonValidator;
this.loanAssembler = loanAssembler; this.loanAssembler = loanAssembler;
@@ -266,6 +271,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransferDetailRepository = accountTransferDetailRepository; this.accountTransferDetailRepository = accountTransferDetailRepository;
this.businessEventNotifierService = businessEventNotifierService; this.businessEventNotifierService = businessEventNotifierService;
this.guarantorDomainService = guarantorDomainService; this.guarantorDomainService = guarantorDomainService;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
} }
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
@@ -354,9 +360,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay); allowTransactionsOnNonWorkingDay);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
recalculateFrom, overdurPenaltyWaitPeriod); recalculateFrom, overdurPenaltyWaitPeriod, floatingRateDTO);
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO); regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -513,7 +521,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
for (LoanRepaymentScheduleInstallment installment : installments) { for (LoanRepaymentScheduleInstallment installment : installments) {
if (installment.getId() == null) { if (installment.getId() == null) {
this.repaymentScheduleInstallmentRepository.save(installment); this.repaymentScheduleInstallmentRepository.save(installment);
} }
} }
this.loanRepository.save(loan); this.loanRepository.save(loan);
} catch (final DataIntegrityViolationException e) { } catch (final DataIntegrityViolationException e) {
@@ -608,9 +616,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays,
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay); allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
firstRepaymentOnDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, firstRepaymentOnDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO); regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive( this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(
@@ -707,9 +717,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
} }
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, 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, final Map<String, Object> changes = loan.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds,
existingReversedTransactionIds, currentUser); existingReversedTransactionIds, currentUser);
@@ -893,11 +904,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.getTransactionDate(); .getTransactionDate();
} }
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay); allowTransactionsOnNonWorkingDay);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail,
defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
@@ -1042,9 +1055,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO,
currentUser); currentUser);
@@ -1137,9 +1152,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
recalculateFrom = command.localDateValueOfParameterNamed("transactionDate"); recalculateFrom = command.localDateValueOfParameterNamed("transactionDate");
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(),
changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO); changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
@@ -1221,9 +1238,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser); existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO, currentUser);
final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L);
@@ -1357,7 +1376,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
boolean isAppliedOnBackDate = false; boolean isAppliedOnBackDate = false;
LoanCharge loanCharge = null; LoanCharge loanCharge = null;
LocalDate recalculateFrom =loan.fetchInterestRecalculateFromDate(); LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate();
if (chargeDefinition.isPercentageOfDisbursementAmount()) { if (chargeDefinition.isPercentageOfDisbursementAmount()) {
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null; LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = null;
for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) { for (LoanDisbursementDetails disbursementDetail : loanDisburseDetails) {
@@ -1478,9 +1497,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Long overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); final Long overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
recalculateFrom, overdurPenaltyWaitPeriod); recalculateFrom, overdurPenaltyWaitPeriod, floatingRateDTO);
ChangedTransactionDetail changedTransactionDetail = loan.handleRegenerateRepaymentScheduleWithInterestRecalculation( ChangedTransactionDetail changedTransactionDetail = loan.handleRegenerateRepaymentScheduleWithInterestRecalculation(
generatorDTO, currentUser); generatorDTO, currentUser);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
@@ -1521,9 +1542,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue());
} }
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
floatingRateDTO);
createLoanScheduleArchive(loan, scheduleGeneratorDTO); createLoanScheduleArchive(loan, scheduleGeneratorDTO);
} }
@@ -1678,9 +1701,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
Money accruedCharge = Money.zero(loan.getCurrency()); Money accruedCharge = Money.zero(loan.getCurrency());
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
@@ -2440,7 +2465,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (loan != null) { if (loan != null) {
boolean reprocessRequired = true; boolean reprocessRequired = true;
LocalDate recalculatedTill = loan.fetchInterestRecalculateFromDate(); LocalDate recalculatedTill = loan.fetchInterestRecalculateFromDate();
if(recalculateFrom.isAfter(recalculatedTill)){ if (recalculateFrom.isAfter(recalculatedTill)) {
recalculateFrom = recalculatedTill; recalculateFrom = recalculatedTill;
} }
@@ -2590,9 +2615,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds,
scheduleGeneratorDTO, currentUser); scheduleGeneratorDTO, currentUser);
@@ -2717,10 +2744,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod(); overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
AppUser currentUser = getAppUserIfPresent(); AppUser currentUser = getAppUserIfPresent();
@@ -2857,19 +2886,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
@Transactional @Transactional
@Override @Override
public void recalculateInterest(final long loanId) { public void recalculateInterest(final long loanId) {
AppUser currentUser = getAppUserIfPresent();
Loan loan = this.loanAssembler.assembleFrom(loanId); Loan loan = this.loanAssembler.assembleFrom(loanId);
LocalDate recalculateFrom = loan.fetchInterestRecalculateFromDate();
AppUser currentUser = getAppUserIfPresent();
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION, this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_INTEREST_RECALCULATION,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
CalendarInstance compoundingCalendarInstance = 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> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -2877,23 +2900,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_REST_DETAIL.getValue());
compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); 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); HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod); overdurPenaltyWaitPeriod, floatingRateDTO);
ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO, ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO,
existingTransactionIds, existingReversedTransactionIds, currentUser); existingTransactionIds, existingReversedTransactionIds, currentUser);
@@ -3022,4 +3048,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return map; 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.data.ChargeData;
import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService;
import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService; 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.data.FundData;
import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
@@ -73,7 +75,9 @@ public class LoanProductsApiResource {
"paymentTypeOptions", "currencyOptions", "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions", "paymentTypeOptions", "currencyOptions", "repaymentFrequencyTypeOptions", "interestRateFrequencyTypeOptions",
"amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions",
"transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions", "accountingRuleOptions", "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", private final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(Arrays.asList("productId", "productName", "restrictedProducts",
"allowedProducts", "productOptions")); "allowedProducts", "productOptions"));
@@ -95,6 +99,7 @@ public class LoanProductsApiResource {
private final ProductMixReadPlatformService productMixReadPlatformService; private final ProductMixReadPlatformService productMixReadPlatformService;
private final DropdownReadPlatformService commonDropdownReadPlatformService; private final DropdownReadPlatformService commonDropdownReadPlatformService;
private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
private final FloatingRatesReadPlatformService floatingRateReadPlatformService;
@Autowired @Autowired
public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService, public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService,
@@ -108,7 +113,8 @@ public class LoanProductsApiResource {
final DefaultToApiJsonSerializer<ProductMixData> productMixDataApiJsonSerializer, final DefaultToApiJsonSerializer<ProductMixData> productMixDataApiJsonSerializer,
final ProductMixReadPlatformService productMixReadPlatformService, final ProductMixReadPlatformService productMixReadPlatformService,
final DropdownReadPlatformService commonDropdownReadPlatformService, final DropdownReadPlatformService commonDropdownReadPlatformService,
PaymentTypeReadPlatformService paymentTypeReadPlatformService) { PaymentTypeReadPlatformService paymentTypeReadPlatformService,
final FloatingRatesReadPlatformService floatingRateReadPlatformService) {
this.context = context; this.context = context;
this.loanProductReadPlatformService = readPlatformService; this.loanProductReadPlatformService = readPlatformService;
this.chargeReadPlatformService = chargeReadPlatformService; this.chargeReadPlatformService = chargeReadPlatformService;
@@ -124,6 +130,7 @@ public class LoanProductsApiResource {
this.productMixReadPlatformService = productMixReadPlatformService; this.productMixReadPlatformService = productMixReadPlatformService;
this.commonDropdownReadPlatformService = commonDropdownReadPlatformService; this.commonDropdownReadPlatformService = commonDropdownReadPlatformService;
this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
this.floatingRateReadPlatformService = floatingRateReadPlatformService;
} }
@POST @POST
@@ -279,13 +286,14 @@ public class LoanProductsApiResource {
.retrieveInterestRecalculationFrequencyTypeOptions(); .retrieveInterestRecalculationFrequencyTypeOptions();
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService final List<EnumOptionData> preCloseInterestCalculationStrategyOptions = dropdownReadPlatformService
.retrivePreCloseInterestCalculationStrategyOptions(); .retrivePreCloseInterestCalculationStrategyOptions();
final List<FloatingRateData> floatingRateOptions = this.floatingRateReadPlatformService.retrieveLookupActive();
return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions,
amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions,
interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions, interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions,
accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions, accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions,
interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions, 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.DaysInYearType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.common.service.CommonEnumerations; 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.fund.data.FundData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInterestRecalculationData; import org.mifosplatform.portfolio.loanaccount.data.LoanInterestRecalculationData;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
@@ -67,6 +68,15 @@ public class LoanProductData {
private final BigDecimal maxInterestRatePerPeriod; private final BigDecimal maxInterestRatePerPeriod;
private final EnumOptionData interestRateFrequencyType; private final EnumOptionData interestRateFrequencyType;
private final BigDecimal annualInterestRate; 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 // settings
private final EnumOptionData amortizationType; private final EnumOptionData amortizationType;
@@ -135,6 +145,8 @@ public class LoanProductData {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private final List<EnumOptionData> interestRecalculationFrequencyTypeOptions; private final List<EnumOptionData> interestRecalculationFrequencyTypeOptions;
@SuppressWarnings("unused")
private final List<FloatingRateData> floatingRateOptions;
private final Boolean multiDisburseLoan; private final Boolean multiDisburseLoan;
private final Integer maxTrancheCount; private final Integer maxTrancheCount;
@@ -165,6 +177,14 @@ public class LoanProductData {
final BigDecimal minInterestRatePerPeriod = null; final BigDecimal minInterestRatePerPeriod = null;
final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null;
final BigDecimal annualInterestRate = 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 repaymentFrequencyType = null;
final EnumOptionData interestRateFrequencyType = null; final EnumOptionData interestRateFrequencyType = null;
final EnumOptionData amortizationType = null; final EnumOptionData amortizationType = null;
@@ -217,7 +237,9 @@ public class LoanProductData {
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, 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 minInterestRatePerPeriod = null;
final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null;
final BigDecimal annualInterestRate = 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 repaymentFrequencyType = null;
final EnumOptionData interestRateFrequencyType = null; final EnumOptionData interestRateFrequencyType = null;
final EnumOptionData amortizationType = null; final EnumOptionData amortizationType = null;
@@ -290,7 +320,9 @@ public class LoanProductData {
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData,
principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, 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 minInterestRatePerPeriod = null;
final BigDecimal maxInterestRatePerPeriod = null; final BigDecimal maxInterestRatePerPeriod = null;
final BigDecimal annualInterestRate = 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 repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(PeriodFrequencyType.MONTHS);
final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(PeriodFrequencyType.MONTHS); final EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(PeriodFrequencyType.MONTHS);
final EnumOptionData amortizationType = LoanEnumerations.amortizationType(AmortizationMethod.EQUAL_INSTALLMENTS); final EnumOptionData amortizationType = LoanEnumerations.amortizationType(AmortizationMethod.EQUAL_INSTALLMENTS);
@@ -371,7 +411,9 @@ public class LoanProductData {
daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment,
holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment, holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment,
accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, 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 Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds,
final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstallment, final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstallment,
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, boolean canDefineInstallmentAmount, 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.id = id;
this.name = name; this.name = name;
this.shortName = shortName; this.shortName = shortName;
@@ -427,6 +472,14 @@ public class LoanProductData {
this.minInterestRatePerPeriod = minInterestRatePerPeriod; this.minInterestRatePerPeriod = minInterestRatePerPeriod;
this.maxInterestRatePerPeriod = maxInterestRatePerPeriod; this.maxInterestRatePerPeriod = maxInterestRatePerPeriod;
this.annualInterestRate = annualInterestRate; 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.repaymentFrequencyType = repaymentFrequencyType;
this.interestRateFrequencyType = interestRateFrequencyType; this.interestRateFrequencyType = interestRateFrequencyType;
this.amortizationType = amortizationType; this.amortizationType = amortizationType;
@@ -457,6 +510,7 @@ public class LoanProductData {
this.interestCalculationPeriodTypeOptions = null; this.interestCalculationPeriodTypeOptions = null;
this.repaymentFrequencyTypeOptions = null; this.repaymentFrequencyTypeOptions = null;
this.interestRateFrequencyTypeOptions = null; this.interestRateFrequencyTypeOptions = null;
this.floatingRateOptions = null;
this.accountingMappingOptions = null; this.accountingMappingOptions = null;
this.accountingRuleOptions = null; this.accountingRuleOptions = null;
@@ -505,7 +559,7 @@ public class LoanProductData {
final List<EnumOptionData> valueConditionTypeOptions, final List<EnumOptionData> daysInMonthTypeOptions, final List<EnumOptionData> valueConditionTypeOptions, final List<EnumOptionData> daysInMonthTypeOptions,
final List<EnumOptionData> daysInYearTypeOptions, final List<EnumOptionData> interestRecalculationCompoundingTypeOptions, final List<EnumOptionData> daysInYearTypeOptions, final List<EnumOptionData> interestRecalculationCompoundingTypeOptions,
final List<EnumOptionData> rescheduleStrategyTypeOptions, final List<EnumOptionData> interestRecalculationFrequencyTypeOptions, 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.id = productData.id;
this.name = productData.name; this.name = productData.name;
this.shortName = productData.shortName; this.shortName = productData.shortName;
@@ -525,6 +579,14 @@ public class LoanProductData {
this.minInterestRatePerPeriod = productData.minInterestRatePerPeriod; this.minInterestRatePerPeriod = productData.minInterestRatePerPeriod;
this.maxInterestRatePerPeriod = productData.maxInterestRatePerPeriod; this.maxInterestRatePerPeriod = productData.maxInterestRatePerPeriod;
this.annualInterestRate = productData.annualInterestRate; 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.repaymentFrequencyType = productData.repaymentFrequencyType;
this.interestRateFrequencyType = productData.interestRateFrequencyType; this.interestRateFrequencyType = productData.interestRateFrequencyType;
this.amortizationType = productData.amortizationType; this.amortizationType = productData.amortizationType;
@@ -552,6 +614,7 @@ public class LoanProductData {
this.currency = productData.currency; this.currency = productData.currency;
this.fundOptions = fundOptions; this.fundOptions = fundOptions;
this.transactionProcessingStrategyOptions = transactionStrategyOptions; this.transactionProcessingStrategyOptions = transactionStrategyOptions;
this.floatingRateOptions = floatingRateOptions;
if (this.transactionProcessingStrategyOptions != null && this.transactionProcessingStrategyOptions.size() == 1) { if (this.transactionProcessingStrategyOptions != null && this.transactionProcessingStrategyOptions.size() == 1) {
final List<TransactionProcessingStrategyData> listOfOptions = new ArrayList<>(this.transactionProcessingStrategyOptions); final List<TransactionProcessingStrategyData> listOfOptions = new ArrayList<>(this.transactionProcessingStrategyOptions);
@@ -886,4 +949,26 @@ public class LoanProductData {
public void setloanProductConfigurableAttributes(LoanProductConfigurableAttributes loanProductConfigurableAttributes) { public void setloanProductConfigurableAttributes(LoanProductConfigurableAttributes loanProductConfigurableAttributes) {
this.allowAttributeOverrides = 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.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; 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.DaysInMonthType;
import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.DaysInYearType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; 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.fund.domain.Fund;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; 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) @Column(name = "instalment_amount_in_multiples_of", nullable = true)
private Integer installmentAmountInMultiplesOf; 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, 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 name = command.stringValueOfParameterNamed("name");
final String shortName = command.stringValueOfParameterNamed(LoanProductConstants.shortName); 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 AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(command.integerValueOfParameterNamed("amortizationType"));
final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command final PeriodFrequencyType repaymentFrequencyType = PeriodFrequencyType.fromInt(command
.integerValueOfParameterNamed("repaymentFrequencyType")); .integerValueOfParameterNamed("repaymentFrequencyType"));
final PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.fromInt(command PeriodFrequencyType interestFrequencyType = PeriodFrequencyType.INVALID;
.integerValueOfParameterNamed("interestRateFrequencyType")); BigDecimal interestRatePerPeriod = null;
final BigDecimal interestRatePerPeriod = command.bigDecimalValueOfParameterNamed("interestRatePerPeriod"); BigDecimal minInterestRatePerPeriod = null;
final BigDecimal minInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("minInterestRatePerPeriod"); BigDecimal maxInterestRatePerPeriod = null;
final BigDecimal maxInterestRatePerPeriod = command.bigDecimalValueOfParameterNamed("maxInterestRatePerPeriod"); BigDecimal annualInterestRate = null;
final BigDecimal annualInterestRate = aprCalculator.calculateFrom(interestFrequencyType, interestRatePerPeriod); 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 repaymentEvery = command.integerValueOfParameterNamed("repaymentEvery");
final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments"); final Integer numberOfRepayments = command.integerValueOfParameterNamed("numberOfRepayments");
@@ -271,7 +301,10 @@ public class LoanProduct extends AbstractPersistable<Long> {
outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment, isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment,
holdGuarantorFunds, loanProductGuaranteeDetails, principalThresholdForLastInstallment, 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 Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds,
final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final BigDecimal principalThresholdForLastInstallment, final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final BigDecimal principalThresholdForLastInstallment,
final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, final boolean canDefineEmiAmount, 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.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy; this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim(); this.name = name.trim();
@@ -513,6 +549,12 @@ public class LoanProduct extends AbstractPersistable<Long> {
this.charges = charges; 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, this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod,
interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery,
repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, graceOnInterestPayment, repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, graceOnInterestPayment,
@@ -603,9 +645,9 @@ public class LoanProduct extends AbstractPersistable<Long> {
public Integer getAccountingType() { public Integer getAccountingType() {
return this.accountingRule; return this.accountingRule;
} }
public List<Charge> getLoanProductCharges(){ public List<Charge> getLoanProductCharges() {
return this.charges; return this.charges;
} }
public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) { public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) {
@@ -616,11 +658,26 @@ public class LoanProduct extends AbstractPersistable<Long> {
return this.loanConfigurableAttributes; 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); final Map<String, Object> actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator);
actualChanges.putAll(loanProductMinMaxConstraints().update(command)); 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"; final String accountingTypeParamName = "accountingRule";
if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, this.accountingRule)) { if (command.isChangeInIntegerParameterNamed(accountingTypeParamName, this.accountingRule)) {
final Integer newValue = command.integerValueOfParameterNamed(accountingTypeParamName); final Integer newValue = command.integerValueOfParameterNamed(accountingTypeParamName);
@@ -907,6 +964,13 @@ public class LoanProduct extends AbstractPersistable<Long> {
return actualChanges; 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() { public boolean isAccountingDisabled() {
return AccountingRuleType.NONE.getValue().equals(this.accountingRule); return AccountingRuleType.NONE.getValue().equals(this.accountingRule);
} }
@@ -1187,4 +1251,21 @@ public class LoanProduct extends AbstractPersistable<Long> {
public LoanProductRelatedDetail getLoanProductRelatedDetail() { public LoanProductRelatedDetail getLoanProductRelatedDetail() {
return loanProductRelatedDetail; 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; 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) @Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal principal; 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; private BigDecimal nominalInterestRatePerPeriod;
// FIXME - move away form JPA ordinal use for enums using just integer - // FIXME - move away form JPA ordinal use for enums using just integer -
// requires sql patch for existing users of software. // requires sql patch for existing users of software.
@Enumerated(EnumType.ORDINAL) @Enumerated(EnumType.ORDINAL)
@Column(name = "interest_period_frequency_enum", nullable = false) @Column(name = "interest_period_frequency_enum", nullable = true)
private PeriodFrequencyType interestPeriodFrequencyType; 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; private BigDecimal annualNominalInterestRate;
// FIXME - move away form JPA ordinal use for enums using just integer - // FIXME - move away form JPA ordinal use for enums using just integer -
@@ -205,17 +205,20 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
@Override @Override
public BigDecimal getNominalInterestRatePerPeriod() { 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 @Override
public PeriodFrequencyType getInterestPeriodFrequencyType() { public PeriodFrequencyType getInterestPeriodFrequencyType() {
return this.interestPeriodFrequencyType; return this.interestPeriodFrequencyType == null? PeriodFrequencyType.INVALID
: this.interestPeriodFrequencyType;
} }
@Override @Override
public BigDecimal getAnnualNominalInterestRate() { 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 @Override
@@ -365,7 +368,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
} }
final String interestRateFrequencyTypeParamName = "interestRateFrequencyType"; 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); final Integer newValue = command.integerValueOfParameterNamed(interestRateFrequencyTypeParamName);
actualChanges.put(interestRateFrequencyTypeParamName, newValue); actualChanges.put(interestRateFrequencyTypeParamName, newValue);
actualChanges.put("locale", localeAsInput); actualChanges.put("locale", localeAsInput);
@@ -567,4 +572,10 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
return this.inArrearsTolerance; 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", "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType",
"interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "graceOnPrincipalPayment", "interestCalculationPeriodType", "inArrearsTolerance", "transactionProcessingStrategyId", "graceOnPrincipalPayment",
"graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule", "includeInBorrowerCycle", "startDate", "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.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.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(), LOAN_PRODUCT_ACCOUNTING_PARAMS.INTEREST_RECEIVABLE.getValue(), LOAN_PRODUCT_ACCOUNTING_PARAMS.LOAN_PORTFOLIO.getValue(),
@@ -214,48 +216,6 @@ public final class LoanProductDataValidator {
Locale.getDefault()); Locale.getDefault());
baseDataValidator.reset().parameter("repaymentFrequencyType").value(repaymentFrequencyType).notNull().inMinMaxRange(0, 3); 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 // settings
final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault()); final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault());
baseDataValidator.reset().parameter("amortizationType").value(amortizationType).notNull().inMinMaxRange(0, 1); 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 // Guarantee Funds
Boolean holdGuaranteeFunds = false; Boolean holdGuaranteeFunds = false;
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
@@ -746,30 +868,6 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter("inArrearsTolerance").value(inArrearsTolerance).ignoreIfNull().zeroOrPositiveAmount(); 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"; final String minNumberOfRepaymentsParameterName = "minNumberOfRepayments";
Integer minNumberOfRepayments = null; Integer minNumberOfRepayments = null;
if (this.fromApiJsonHelper.parameterExists(minNumberOfRepaymentsParameterName, element)) { 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)) { if (this.fromApiJsonHelper.parameterExists("amortizationType", element)) {
final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault()); final Integer amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault());
baseDataValidator.reset().parameter("amortizationType").value(amortizationType).notNull().inMinMaxRange(0, 1); 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 // Guarantee Funds
Boolean holdGuaranteeFunds = loanProduct.isHoldGuaranteeFundsEnabled(); Boolean holdGuaranteeFunds = loanProduct.isHoldGuaranteeFundsEnabled();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
@@ -1303,6 +1565,11 @@ public final class LoanProductDataValidator {
private void validateNominalInterestRatePerPeriodMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct, private void validateNominalInterestRatePerPeriodMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct,
final DataValidatorBuilder baseDataValidator) { final DataValidatorBuilder baseDataValidator) {
if((this.fromApiJsonHelper.parameterExists("isLinkedToFloatingInterestRates", element)
&& this.fromApiJsonHelper.extractBooleanNamed("isLinkedToFloatingInterestRates", element) == true)
|| loanProduct.isLinkedToFloatingInterestRate()){
return;
}
boolean iRPUpdated = false; boolean iRPUpdated = false;
boolean minIRPUpdated = false; boolean minIRPUpdated = false;
boolean maxIRPUpdated = false; boolean maxIRPUpdated = false;
@@ -199,7 +199,7 @@ public class LoanEnumerations {
return optionData; return optionData;
} }
public static EnumOptionData interestRateFrequencyType(final int id) { public static EnumOptionData interestRateFrequencyType(final Integer id) {
return interestRateFrequencyType(PeriodFrequencyType.fromInt(id)); return interestRateFrequencyType(PeriodFrequencyType.fromInt(id));
} }
@@ -503,6 +503,10 @@ public class LoanEnumerations {
optionData = new EnumOptionData(LoanTermVariationType.EMI_AMOUNT.getValue().longValue(), optionData = new EnumOptionData(LoanTermVariationType.EMI_AMOUNT.getValue().longValue(),
LoanTermVariationType.EMI_AMOUNT.getCode(), "emiAmount"); LoanTermVariationType.EMI_AMOUNT.getCode(), "emiAmount");
break; break;
case INTEREST_RATE:
optionData = new EnumOptionData(LoanTermVariationType.INTEREST_RATE.getValue().longValue(),
LoanTermVariationType.INTEREST_RATE.getCode(), "interestRate");
break;
default: default:
optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(), optionData = new EnumOptionData(LoanTermVariationType.INVALID.getValue().longValue(),
LoanTermVariationType.INVALID.getCode(), "Invalid"); 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, " + "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.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.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 " + " from m_product_loan lp "
+ " left join m_fund f on f.id = lp.fund_id " + " 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_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 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 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_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"; + " 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 maxInterestRatePerPeriod = rs.getBigDecimal("maxInterestRatePerPeriod");
final BigDecimal annualInterestRate = rs.getBigDecimal("annualInterestRate"); 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 int repaymentFrequencyTypeId = JdbcSupport.getInteger(rs, "repaymentPeriodFrequency");
final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeId); final EnumOptionData repaymentFrequencyType = LoanEnumerations.repaymentFrequencyType(repaymentFrequencyTypeId);
final int amortizationTypeId = JdbcSupport.getInteger(rs, "amortizationMethod"); final int amortizationTypeId = JdbcSupport.getInteger(rs, "amortizationMethod");
final EnumOptionData amortizationType = LoanEnumerations.amortizationType(amortizationTypeId); 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 EnumOptionData interestRateFrequencyType = LoanEnumerations.interestRateFrequencyType(interestRateFrequencyTypeId);
final int interestTypeId = JdbcSupport.getInteger(rs, "interestMethod"); final int interestTypeId = JdbcSupport.getInteger(rs, "interestMethod");
@@ -377,7 +396,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData,
principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, 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.infrastructure.security.service.PlatformSecurityContext;
import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; 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.Fund;
import org.mifosplatform.portfolio.fund.domain.FundRepository; import org.mifosplatform.portfolio.fund.domain.FundRepository;
import org.mifosplatform.portfolio.fund.exception.FundNotFoundException; 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.domain.LoanTransactionProcessingStrategyRepository;
import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException; import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; 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.LoanProductRepository;
import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy;
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; 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.LoanProductDateException;
import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductDataValidator; import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductDataValidator;
@@ -57,6 +61,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
private final ChargeRepositoryWrapper chargeRepository; private final ChargeRepositoryWrapper chargeRepository;
private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService; private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
private final MifosEntityAccessUtil mifosEntityAccessUtil; private final MifosEntityAccessUtil mifosEntityAccessUtil;
private final FloatingRateRepositoryWrapper floatingRateRepository;
private final LoanRepository loanRepository;
@Autowired @Autowired
public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -65,7 +71,9 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository, final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository,
final ChargeRepositoryWrapper chargeRepository, final ChargeRepositoryWrapper chargeRepository,
final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService, final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
final MifosEntityAccessUtil mifosEntityAccessUtil) { final MifosEntityAccessUtil mifosEntityAccessUtil,
final FloatingRateRepositoryWrapper floatingRateRepository,
final LoanRepository loanRepository) {
this.context = context; this.context = context;
this.fromApiJsonDeserializer = fromApiJsonDeserializer; this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanProductRepository = loanProductRepository; this.loanProductRepository = loanProductRepository;
@@ -75,6 +83,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
this.chargeRepository = chargeRepository; this.chargeRepository = chargeRepository;
this.accountMappingWritePlatformService = accountMappingWritePlatformService; this.accountMappingWritePlatformService = accountMappingWritePlatformService;
this.mifosEntityAccessUtil = mifosEntityAccessUtil; this.mifosEntityAccessUtil = mifosEntityAccessUtil;
this.floatingRateRepository = floatingRateRepository;
this.loanRepository = loanRepository;
} }
@Transactional @Transactional
@@ -96,8 +106,13 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
final String currencyCode = command.stringValueOfParameterNamed("currencyCode"); final String currencyCode = command.stringValueOfParameterNamed("currencyCode");
final List<Charge> charges = assembleListOfProductCharges(command, 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, final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command,
this.aprCalculator); this.aprCalculator, floatingRate);
loanproduct.updateLoanProductInRelatedClasses(); loanproduct.updateLoanProductInRelatedClasses();
this.loanProductRepository.save(loanproduct); this.loanProductRepository.save(loanproduct);
@@ -154,7 +169,18 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
this.fromApiJsonDeserializer.validateForUpdate(command.json(), product); this.fromApiJsonDeserializer.validateForUpdate(command.json(), product);
validateInputDates(command); 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")) { if (changes.containsKey("fundId")) {
final Long fundId = (Long) changes.get("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<>(); 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);