FINERACT-1932: Fineract modularization - fineract-rates

This commit is contained in:
Jose Alberto Hernandez
2024-05-17 15:12:02 +02:00
committed by Adam Saghy
parent 8b2c9da804
commit abc7cec2ec
30 changed files with 293 additions and 0 deletions
@@ -1,79 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.floatingrates.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import org.apache.fineract.infrastructure.core.service.DateUtils;
public class FloatingRateDTO {
private final boolean isFloatingInterestRate;
private final LocalDate startDate;
private BigDecimal interestRateDiff;
private BigDecimal actualInterestRateDiff;
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.actualInterestRateDiff = interestRateDiff;
this.baseLendingRatePeriods = baseLendingRatePeriods;
}
public BigDecimal fetchBaseRate(LocalDate date) {
BigDecimal rate = null;
for (FloatingRatePeriodData periodData : this.baseLendingRatePeriods) {
final LocalDate periodFromDate = periodData.getFromDate();
if (!DateUtils.isAfter(periodFromDate, date)) {
rate = periodData.getInterestRate();
break;
}
}
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;
}
public void resetInterestRateDiff() {
this.interestRateDiff = this.actualInterestRateDiff;
}
}
@@ -1,142 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.floatingrates.data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class FloatingRatePeriodData implements Comparable<FloatingRatePeriodData>, Serializable {
private Long id;
private LocalDate fromDate;
private BigDecimal interestRate;
private boolean isDifferentialToBaseLendingRate;
private boolean isActive;
private String createdBy;
private OffsetDateTime createdOn;
private String modifiedBy;
private OffsetDateTime modifiedOn;
public FloatingRatePeriodData(Long id, LocalDate fromDate, BigDecimal interestRate, boolean isDifferentialToBaseLendingRate,
boolean isActive, String createdBy, OffsetDateTime createdOn, String modifiedBy, OffsetDateTime 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 FloatingRatePeriodData(Long id, LocalDate fromDate, BigDecimal interestRate, boolean isDifferentialToBaseLendingRate,
boolean isActive) {
this.id = id;
this.fromDate = fromDate;
this.interestRate = interestRate;
this.isDifferentialToBaseLendingRate = isDifferentialToBaseLendingRate;
this.isActive = isActive;
}
public Long getId() {
return this.id;
}
public LocalDate getFromDate() {
return this.fromDate;
}
public LocalDate getFromDateAsLocalDate() {
return 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 OffsetDateTime getCreatedOn() {
return this.createdOn;
}
public String getModifiedBy() {
return this.modifiedBy;
}
public OffsetDateTime 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 instanceof FloatingRatePeriodData)) {
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();
}
}
@@ -1,211 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.floatingrates.domain;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
@Entity
@Table(name = "m_floating_rates", uniqueConstraints = { @UniqueConstraint(columnNames = { "name" }, name = "unq_name") })
public class FloatingRate extends AbstractAuditableWithUTCDateTimeCustom {
@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")
@OneToMany(cascade = CascadeType.ALL, mappedBy = "floatingRate", orphanRemoval = true, fetch = FetchType.EAGER)
private List<FloatingRatePeriod> floatingRatePeriods;
/*
* Deprecated since common Auditable fields were introduced. Columns and data left untouched to help migration.
*
* @Column(name = "created_date", nullable = false) private LocalDateTime createdOn;
*
* @Column(name = "lastmodified_date", nullable = false) private LocalDateTime modifiedOn;
*/
public FloatingRate() {
}
public FloatingRate(String name, boolean isBaseLendingRate, boolean isActive, List<FloatingRatePeriod> floatingRatePeriods) {
this.name = name;
this.isBaseLendingRate = isBaseLendingRate;
this.isActive = isActive;
this.floatingRatePeriods = floatingRatePeriods;
if (floatingRatePeriods != null) {
for (FloatingRatePeriod ratePeriod : floatingRatePeriods) {
ratePeriod.updateFloatingRate(this);
}
}
}
public static FloatingRate createNew(JsonCommand command) {
final String name = command.stringValueOfParameterNamed("name");
final boolean isBaseLendingRate = command.parameterExists("isBaseLendingRate")
&& command.booleanPrimitiveValueOfParameterNamed("isBaseLendingRate");
final boolean isActive = !command.parameterExists("isActive") || command.booleanPrimitiveValueOfParameterNamed("isActive");
final List<FloatingRatePeriod> floatingRatePeriods = getRatePeriods(command);
return new FloatingRate(name, isBaseLendingRate, isActive, floatingRatePeriods);
}
private static List<FloatingRatePeriod> getRatePeriods(final JsonCommand command) {
if (!command.parameterExists("ratePeriods")) {
return null;
}
List<FloatingRatePeriod> ratePeriods = new ArrayList<>();
JsonArray arrayOfParameterNamed = command.arrayOfParameterNamed("ratePeriods");
for (final JsonElement ratePeriod : arrayOfParameterNamed) {
final JsonObject ratePeriodObject = ratePeriod.getAsJsonObject();
final JsonParserHelper helper = new JsonParserHelper();
final LocalDate fromDate = helper.extractLocalDateNamed("fromDate", ratePeriod, new HashSet<String>());
final BigDecimal interestRate = ratePeriodObject.get("interestRate").getAsBigDecimal();
final boolean isDifferentialToBaseLendingRate = helper.parameterExists("isDifferentialToBaseLendingRate", ratePeriod)
&& ratePeriodObject.get("isDifferentialToBaseLendingRate").getAsBoolean();
final boolean isActive = true;
ratePeriods.add(new FloatingRatePeriod(fromDate, interestRate, isDifferentialToBaseLendingRate, isActive));
}
return ratePeriods;
}
public String getName() {
return this.name;
}
public boolean isBaseLendingRate() {
return this.isBaseLendingRate;
}
public boolean isActive() {
return this.isActive;
}
public List<FloatingRatePeriod> getFloatingRatePeriods() {
return this.floatingRatePeriods;
}
public Map<String, Object> update(final JsonCommand command) {
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 List<FloatingRatePeriod> newRatePeriods = getRatePeriods(command);
if (newRatePeriods != null && !newRatePeriods.isEmpty()) {
updateRatePeriods(newRatePeriods);
actualChanges.put("ratePeriods", command.jsonFragment("ratePeriods"));
}
return actualChanges;
}
private void updateRatePeriods(final List<FloatingRatePeriod> newRatePeriods) {
final LocalDate today = DateUtils.getBusinessLocalDate();
if (this.floatingRatePeriods != null) {
for (FloatingRatePeriod ratePeriod : this.floatingRatePeriods) {
LocalDate fromDate = ratePeriod.getFromDate();
if (DateUtils.isAfter(fromDate, today)) {
ratePeriod.setActive(false);
}
}
}
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() && DateUtils.isBefore(floatingRateDTO.getStartDate(), 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;
}
}
@@ -1,114 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.floatingrates.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
@Entity
@Table(name = "m_floating_rates_periods")
public class FloatingRatePeriod extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne
@JoinColumn(name = "floating_rates_id", nullable = false)
private FloatingRate floatingRate;
@Column(name = "from_date", nullable = false)
private LocalDate 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;
/*
* Deprecated since common Auditable fields were introduced. Columns and data left untouched to help migration.
*
* @Column(name = "created_date", nullable = false) private LocalDateTime createdOn;
*
* @Column(name = "lastmodified_date", nullable = false) private LocalDateTime modifiedOn;
*/
public FloatingRatePeriod() {
}
public FloatingRatePeriod(LocalDate fromDate, BigDecimal interestRate, boolean isDifferentialToBaseLendingRate, boolean isActive) {
this.fromDate = fromDate;
this.interestRate = interestRate;
this.isDifferentialToBaseLendingRate = isDifferentialToBaseLendingRate;
this.isActive = isActive;
}
public void updateFloatingRate(FloatingRate floatingRate) {
this.floatingRate = floatingRate;
}
public FloatingRate getFloatingRate() {
return this.floatingRate;
}
public LocalDate getFromDate() {
return this.fromDate;
}
public BigDecimal getInterestRate() {
return this.interestRate;
}
public boolean isDifferentialToBaseLendingRate() {
return this.isDifferentialToBaseLendingRate;
}
public boolean isActive() {
return this.isActive;
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
public LocalDate fetchFromDate() {
return fromDate;
}
public FloatingRatePeriodData toData(final FloatingRateDTO floatingRateDTO) {
BigDecimal interest = getInterestRate().add(floatingRateDTO.getInterestRateDiff());
if (isDifferentialToBaseLendingRate()) {
interest = interest.add(floatingRateDTO.fetchBaseRate(fetchFromDate()));
}
final LocalDate fromDate = getFromDate();
return new FloatingRatePeriodData(getId(), fromDate, interest, isDifferentialToBaseLendingRate(), isActive());
}
}