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
@@ -0,0 +1,133 @@
/**
* 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.api;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.springframework.stereotype.Component;
@Path("/v1/floatingrates")
@Component
@Tag(name = "Floating Rates", description = "It lets you create, list, retrieve and upload the floating rates")
@RequiredArgsConstructor
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;
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create a new Floating Rate", description = "Creates a new Floating Rate\n" + "Mandatory Fields: name\n"
+ "Optional Fields: isBaseLendingRate, isActive, ratePeriods")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = FloatingRatesApiResourceSwagger.PostFloatingRatesRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FloatingRatesApiResourceSwagger.PostFloatingRatesResponse.class))) })
public String createFloatingRate(@Parameter(hidden = true) 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 })
@Operation(summary = "List Floating Rates", description = "Lists Floating Rates")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = FloatingRatesApiResourceSwagger.GetFloatingRatesResponse.class)))) })
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 })
@Operation(summary = "Retrieve Floating Rate", description = "Retrieves Floating Rate")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FloatingRatesApiResourceSwagger.GetFloatingRatesFloatingRateIdResponse.class))) })
public String retrieveOne(@PathParam("floatingRateId") @Parameter(description = "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 })
@Operation(summary = "Update Floating Rate", description = "Updates new Floating Rate. Rate Periods in the past cannot be modified. All the future rateperiods would be replaced with the new ratePeriods data sent.")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = FloatingRatesApiResourceSwagger.PutFloatingRatesFloatingRateIdRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = FloatingRatesApiResourceSwagger.PutFloatingRatesFloatingRateIdResponse.class))) })
public String updateFloatingRate(@PathParam("floatingRateId") @Parameter(description = "floatingRateId") final Long floatingRateId,
@Parameter(hidden = true) 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,169 @@
/**
* 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.api;
import io.swagger.v3.oas.annotations.media.Schema;
import java.util.Set;
/**
* Created by Chirag Gupta on 12/17/17.
*/
final class FloatingRatesApiResourceSwagger {
private FloatingRatesApiResourceSwagger() {}
@Schema(description = "PostFloatingRatesRequest")
public static final class PostFloatingRatesRequest {
private PostFloatingRatesRequest() {}
static final class PostFloatingRatesRatePeriods {
private PostFloatingRatesRatePeriods() {}
@Schema(example = "19 November 2015")
public String fromDate;
@Schema(example = "10")
public Double interestRate;
@Schema(example = "en")
public String locale;
@Schema(example = "dd MMMM yyyy")
public String dateFormat;
}
@Schema(example = "Floating Rate 1")
public String name;
@Schema(example = "true")
public Boolean isBaseLendingRate;
@Schema(example = "true")
public Boolean isActive;
public Set<PostFloatingRatesRatePeriods> ratePeriods;
}
@Schema(description = "PostFloatingRatesResponse")
public static final class PostFloatingRatesResponse {
private PostFloatingRatesResponse() {}
@Schema(example = "1")
public Long resourceId;
}
@Schema(description = "GetFloatingRatesResponse")
public static final class GetFloatingRatesResponse {
private GetFloatingRatesResponse() {}
@Schema(example = "1")
public Long id;
@Schema(example = "Floating Rate 1")
public String name;
@Schema(example = "true")
public Boolean isBaseLendingRate;
@Schema(example = "true")
public Boolean isActive;
@Schema(example = "mifos")
public String createdBy;
@Schema(example = "Nov 18, 2015")
public String createdOn;
@Schema(example = "mifos")
public String modifiedBy;
@Schema(example = "Nov 18, 2015")
public String modifiedOn;
}
@Schema(description = "GetFloatingRatesFloatingRateIdResponse")
public static final class GetFloatingRatesFloatingRateIdResponse {
private GetFloatingRatesFloatingRateIdResponse() {}
static final class GetFloatingRatesRatePeriods {
private GetFloatingRatesRatePeriods() {}
@Schema(example = "1")
public Long id;
@Schema(example = "Dec 15, 2015")
public String fromDate;
@Schema(example = "11")
public Double interestRate;
@Schema(example = "false")
public Boolean isDifferentialToBaseLendingRate;
@Schema(example = "true")
public Boolean isActive;
@Schema(example = "mifos")
public String createdBy;
@Schema(example = "Nov 18, 2015")
public String createdOn;
@Schema(example = "mifos")
public String modifiedBy;
@Schema(example = "Nov 18, 2015")
public String modifiedOn;
}
@Schema(example = "1")
public Long id;
@Schema(example = "Floating Rate 1")
public String name;
@Schema(example = "true")
public Boolean isBaseLendingRate;
@Schema(example = "true")
public Boolean isActive;
@Schema(example = "mifos")
public String createdBy;
@Schema(example = "Nov 18, 2015")
public String createdOn;
@Schema(example = "mifos")
public String modifiedBy;
@Schema(example = "Nov 18, 2015")
public String modifiedOn;
public Set<GetFloatingRatesRatePeriods> ratePeriods;
}
@Schema(description = "PutFloatingRatesFloatingRateIdRequest")
public static final class PutFloatingRatesFloatingRateIdRequest {
private PutFloatingRatesFloatingRateIdRequest() {}
@Schema(example = "Floating Rate 1")
public String name;
@Schema(example = "true")
public Boolean isBaseLendingRate;
@Schema(example = "true")
public Boolean isActive;
public Set<PostFloatingRatesRequest.PostFloatingRatesRatePeriods> ratePeriods;
}
@Schema(description = "PutFloatingRatesFloatingRateIdResponse")
public static final class PutFloatingRatesFloatingRateIdResponse {
private PutFloatingRatesFloatingRateIdResponse() {}
static final class PutFloatingRatesChanges {
private PutFloatingRatesChanges() {}
public Set<PostFloatingRatesRequest.PostFloatingRatesRatePeriods> ratePeriods;
}
@Schema(example = "1")
public Long resourceId;
public PutFloatingRatesChanges changes;
}
}
@@ -0,0 +1,79 @@
/**
* 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;
}
}
@@ -0,0 +1,141 @@
/**
* 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.time.OffsetDateTime;
import java.util.List;
import org.apache.commons.lang3.builder.CompareToBuilder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
public class FloatingRateData implements Comparable<FloatingRateData>, Serializable {
private final Long id;
private final String name;
private final boolean isBaseLendingRate;
private final boolean isActive;
private final String createdBy;
private final OffsetDateTime createdOn;
private final String modifiedBy;
private final OffsetDateTime 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, OffsetDateTime createdOn,
String modifiedBy, OffsetDateTime 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 OffsetDateTime getCreatedOn() {
return this.createdOn;
}
public String getModifiedBy() {
return this.modifiedBy;
}
public OffsetDateTime 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 instanceof FloatingRateData)) {
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,142 @@
/**
* 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();
}
}
@@ -0,0 +1,69 @@
/**
* 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 lombok.Getter;
@Getter
public class InterestRatePeriodData {
private LocalDate fromDate;
private final BigDecimal interestRate;
private final boolean isDifferentialToBLR;
private final LocalDate blrFromDate;
private final BigDecimal blrInterestRate;
private BigDecimal loanDifferentialInterestRate;
private BigDecimal loanProductDifferentialInterestRate;
private BigDecimal effectiveInterestRate;
public InterestRatePeriodData(LocalDate fromDate, BigDecimal interestRate, boolean isDifferentialToBLR, LocalDate blrFromDate,
BigDecimal blrInterestRate) {
this.fromDate = fromDate;
this.interestRate = interestRate;
this.isDifferentialToBLR = isDifferentialToBLR;
this.blrFromDate = blrFromDate;
this.blrInterestRate = blrInterestRate;
}
public void setFromDate(LocalDate fromDate) {
this.fromDate = fromDate;
}
public boolean isDifferentialToBLR() {
return this.isDifferentialToBLR;
}
public boolean isIsDifferentialToBLR() {
return this.isDifferentialToBLR;
}
public void setLoanDifferentialInterestRate(BigDecimal loanDifferentialInterestRate) {
this.loanDifferentialInterestRate = loanDifferentialInterestRate;
}
public void setLoanProductDifferentialInterestRate(BigDecimal loanProductDifferentialInterestRate) {
this.loanProductDifferentialInterestRate = loanProductDifferentialInterestRate;
}
public void setEffectiveInterestRate(BigDecimal effectiveInterestRate) {
this.effectiveInterestRate = effectiveInterestRate;
}
}
@@ -0,0 +1,211 @@
/**
* 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;
}
}
@@ -0,0 +1,114 @@
/**
* 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());
}
}
@@ -0,0 +1,36 @@
/**
* 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 java.util.Collection;
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("select floatingRate from FloatingRate floatingRate where floatingRate.isBaseLendingRate = true and floatingRate.isActive = true")
FloatingRate retrieveBaseLendingRate();
@Query("select floatingRate from FloatingRate floatingRate " + " inner join floatingRate.floatingRatePeriods as periods"
+ " where floatingRate.isActive = true " + " and periods.isActive = true "
+ " and periods.isDifferentialToBaseLendingRate = true")
Collection<FloatingRate> retrieveFloatingRatesLinkedToBLR();
}
@@ -0,0 +1,50 @@
/**
* 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 org.apache.fineract.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) {
return this.floatingRateRepository.findById(id).orElseThrow(() -> new FloatingRateNotFoundException(id));
}
public void save(FloatingRate newFloatingRate) {
this.floatingRateRepository.save(newFloatingRate);
}
public void saveAndFlush(FloatingRate floatingRateForUpdate) {
this.floatingRateRepository.saveAndFlush(floatingRateForUpdate);
}
}
@@ -0,0 +1,41 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
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");
}
public FloatingRateNotFoundException(String globalisationMessageCode, EmptyResultDataAccessException e) {
super(globalisationMessageCode, "Floating Rate does not exist", e);
}
public FloatingRateNotFoundException(Long id, EmptyResultDataAccessException e) {
super("error.msg.floatingrate.id.invalid", "Floating Rate with identifier " + id + " does not exist", id, e);
}
}
@@ -0,0 +1,46 @@
/**
* 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.handler;
import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.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,46 @@
/**
* 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.handler;
import org.apache.fineract.commands.annotation.CommandType;
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.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,248 @@
/**
* 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.serialization;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class FloatingRateDataValidator {
public static final String NAME = "name";
public static final String IS_BASE_LENDING_RATE = "isBaseLendingRate";
public static final String IS_ACTIVE = "isActive";
public static final String RATE_PERIODS = "ratePeriods";
public static final String FROM_DATE = "fromDate";
public static final String INTEREST_RATE = "interestRate";
public static final String IS_DIFFERENTIAL_TO_BASE_LENDING_RATE = "isDifferentialToBaseLendingRate";
public static final String LOCALE = "locale";
public static final String DATE_FORMAT = "dateFormat";
public static final String FLOATINGRATE = "floatingrate";
private static final Set<String> SUPPORTED_PARAMETERS_FOR_FLOATING_RATES = new HashSet<>(
Arrays.asList(NAME, IS_BASE_LENDING_RATE, IS_ACTIVE, RATE_PERIODS));
private static final Set<String> SUPPORTED_PARAMETERS_FOR_FLOATING_RATE_PERIODS = new HashSet<>(
Arrays.asList(FROM_DATE, INTEREST_RATE, IS_DIFFERENTIAL_TO_BASE_LENDING_RATE, LOCALE, DATE_FORMAT));
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, SUPPORTED_PARAMETERS_FOR_FLOATING_RATES);
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);
Boolean isBaseLendingRate = null;
if (this.fromApiJsonHelper.parameterExists(IS_BASE_LENDING_RATE, element)) {
isBaseLendingRate = this.fromApiJsonHelper.extractBooleanNamed(IS_BASE_LENDING_RATE, element);
baseDataValidator.reset().parameter(IS_BASE_LENDING_RATE).value(isBaseLendingRate).notNull();
if (isBaseLendingRate == null) {
baseDataValidator.reset().parameter(IS_BASE_LENDING_RATE).trueOrFalseRequired(false);
} else if (isBaseLendingRate) {
FloatingRate baseLendingRate = this.floatingRateRepository.retrieveBaseLendingRate();
if (baseLendingRate != null) {
baseDataValidator.reset().parameter(IS_BASE_LENDING_RATE).value(isBaseLendingRate)
.failWithCode("baselendingrate.duplicate", "Base Lending Rate already exists");
}
}
}
if (this.fromApiJsonHelper.parameterExists(IS_ACTIVE, element)) {
final Boolean isActive = this.fromApiJsonHelper.extractBooleanNamed(IS_ACTIVE, element);
if (isActive == null) {
baseDataValidator.reset().parameter(IS_ACTIVE).trueOrFalseRequired(false);
}
}
if (isBaseLendingRate == null) {
isBaseLendingRate = false;
}
validateRatePeriods(baseDataValidator, element, isBaseLendingRate, false);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void validateRatePeriods(DataValidatorBuilder baseDataValidator, JsonElement element, boolean isBaseLendingRate,
boolean isBLRModifiedAsNonBLR) {
if (this.fromApiJsonHelper.parameterExists(RATE_PERIODS, element)) {
final JsonArray ratePeriods = this.fromApiJsonHelper.extractJsonArrayNamed(RATE_PERIODS, element);
baseDataValidator.reset().parameter(RATE_PERIODS).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(),
SUPPORTED_PARAMETERS_FOR_FLOATING_RATE_PERIODS);
final LocalDate fromDate = this.fromApiJsonHelper.extractLocalDateNamed(FROM_DATE, ratePeriod);
baseDataValidator.reset().parameter(FROM_DATE).parameterAtIndexArray(FROM_DATE, i + 1).value(fromDate).notBlank()
.validateDateAfter(DateUtils.getBusinessLocalDate().plusDays(1));
if (fromDate != null) {
fromDates.add(fromDate);
}
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE,
ratePeriod);
baseDataValidator.reset().parameter(INTEREST_RATE).parameterAtIndexArray(INTEREST_RATE, i + 1)
.value(interestRatePerPeriod).notNull().zeroOrPositiveAmount();
if (this.fromApiJsonHelper.parameterExists(IS_DIFFERENTIAL_TO_BASE_LENDING_RATE, ratePeriod)) {
final Boolean isDifferentialToBaseLendingRate = this.fromApiJsonHelper
.extractBooleanNamed(IS_DIFFERENTIAL_TO_BASE_LENDING_RATE, ratePeriod);
if (isDifferentialToBaseLendingRate == null) {
baseDataValidator.reset().parameter(IS_DIFFERENTIAL_TO_BASE_LENDING_RATE)
.parameterAtIndexArray(IS_DIFFERENTIAL_TO_BASE_LENDING_RATE, i + 1).trueOrFalseRequired(false);
} else if (isDifferentialToBaseLendingRate) {
FloatingRate baseLendingRate = this.floatingRateRepository.retrieveBaseLendingRate();
if (baseLendingRate == null || isBLRModifiedAsNonBLR) {
baseDataValidator.reset().parameter(IS_DIFFERENTIAL_TO_BASE_LENDING_RATE)
.parameterAtIndexArray(IS_DIFFERENTIAL_TO_BASE_LENDING_RATE, i + 1)
.value(isDifferentialToBaseLendingRate)
.failWithCode("no.baselending.rate.defined", "Base Lending Rate doesn't exists");
}
if (isBaseLendingRate) {
baseDataValidator.reset().parameter(IS_DIFFERENTIAL_TO_BASE_LENDING_RATE)
.parameterAtIndexArray(IS_DIFFERENTIAL_TO_BASE_LENDING_RATE, i + 1)
.value(isDifferentialToBaseLendingRate).failWithCode("cannot.be.true.for.baselendingrate",
"isDifferentialToBaseLendingRate cannot be true for floating rate marked as Base Lending Rate.");
}
}
}
}
Set<LocalDate> uniqueFromDates = new HashSet<>(fromDates);
if (fromDates.size() != uniqueFromDates.size()) {
baseDataValidator.reset().parameter(FROM_DATE).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, SUPPORTED_PARAMETERS_FOR_FLOATING_RATES);
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);
}
Boolean isBaseLendingRate = null;
Boolean isBLRModifiedAsNonBLR = false;
FloatingRate baseLendingRate = this.floatingRateRepository.retrieveBaseLendingRate();
if (this.fromApiJsonHelper.parameterExists(IS_BASE_LENDING_RATE, element)) {
isBaseLendingRate = this.fromApiJsonHelper.extractBooleanNamed(IS_BASE_LENDING_RATE, element);
baseDataValidator.reset().parameter(IS_BASE_LENDING_RATE).value(isBaseLendingRate).notNull();
if (isBaseLendingRate == null) {
baseDataValidator.reset().parameter(IS_BASE_LENDING_RATE).trueOrFalseRequired(false);
} else if (isBaseLendingRate && baseLendingRate != null && !baseLendingRate.getId().equals(floatingRateForUpdate.getId())) { // NOSONAR
baseDataValidator.reset().parameter(IS_BASE_LENDING_RATE).value(isBaseLendingRate).failWithCode("baselendingrate.duplicate",
"Base Lending Rate already exists");
}
}
Boolean isActive = null;
if (this.fromApiJsonHelper.parameterExists(IS_ACTIVE, element)) {
isActive = this.fromApiJsonHelper.extractBooleanNamed(IS_ACTIVE, element);
if (isActive == null) {
baseDataValidator.reset().parameter(IS_ACTIVE).trueOrFalseRequired(false);
}
}
if (isBaseLendingRate == null) {
isBaseLendingRate = floatingRateForUpdate.isBaseLendingRate();
}
if (isActive == null) {
isActive = floatingRateForUpdate.isActive();
}
if (baseLendingRate != null && baseLendingRate.getId().equals(floatingRateForUpdate.getId()) && (!isBaseLendingRate || !isActive)) { // NOSONAR
isBLRModifiedAsNonBLR = true;
}
if (isBLRModifiedAsNonBLR) {
Collection<FloatingRate> floatingRates = this.floatingRateRepository.retrieveFloatingRatesLinkedToBLR();
if (floatingRates != null && !floatingRates.isEmpty()) {
baseDataValidator.reset().parameter(IS_BASE_LENDING_RATE).value(isBaseLendingRate).failWithCode(
"cannot.be.marked.non.baselendingrate",
"There are floating rates linked to this Base Lending Rate, cannot be marked as non-Base Lending Rate.");
}
}
validateRatePeriods(baseDataValidator, element, isBaseLendingRate, isBLRModifiedAsNonBLR);
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,29 @@
/**
* 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.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface FloatingRateWritePlatformService {
CommandProcessingResult createFloatingRate(JsonCommand command);
CommandProcessingResult updateFloatingRate(JsonCommand command);
}
@@ -0,0 +1,107 @@
/**
* 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.service;
import jakarta.persistence.PersistenceException;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper;
import org.apache.fineract.portfolio.floatingrates.serialization.FloatingRateDataValidator;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.orm.jpa.JpaSystemException;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Slf4j
public class FloatingRateWritePlatformServiceImpl implements FloatingRateWritePlatformService {
private final FloatingRateDataValidator fromApiJsonDeserializer;
private final FloatingRateRepositoryWrapper floatingRateRepository;
@Transactional
@Override
public CommandProcessingResult createFloatingRate(final JsonCommand command) {
try {
this.fromApiJsonDeserializer.validateForCreate(command.json());
final FloatingRate newFloatingRate = FloatingRate.createNew(command);
this.floatingRateRepository.saveAndFlush(newFloatingRate);
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(newFloatingRate.getId()) //
.build();
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
handleDataIntegrityIssues(command, throwable, 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 Map<String, Object> changes = floatingRateForUpdate.update(command);
if (!changes.isEmpty()) {
this.floatingRateRepository.save(floatingRateForUpdate);
}
return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) //
.withEntityId(command.entityId()) //
.with(changes) //
.build();
} catch (final JpaSystemException | DataIntegrityViolationException dve) {
handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve);
return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
Throwable throwable = ExceptionUtils.getRootCause(dve.getCause());
handleDataIntegrityIssues(command, throwable, dve);
return CommandProcessingResult.empty();
}
}
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dve) {
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", "");
}
log.error("Error occured.", dve);
throw ErrorHandler.getMappable(dve, "error.msg.floatingrates.unknown.data.integrity.issue",
"Unknown data integrity issue with resource: " + realCause.getMessage());
}
}
@@ -0,0 +1,39 @@
/**
* 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.service;
import java.util.List;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.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 productId);
}
@@ -0,0 +1,239 @@
/**
* 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.service;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@RequiredArgsConstructor
public class FloatingRatesReadPlatformServiceImpl implements FloatingRatesReadPlatformService {
private final JdbcTemplate jdbcTemplate;
@Override
public List<FloatingRateData> retrieveAll() {
FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(false);
final String sql = "select " + rateMapper.schema();
return this.jdbcTemplate.query(sql, rateMapper); // NOSONAR
}
@Override
public List<FloatingRateData> retrieveAllActive() {
FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(false);
final String sql = "select " + rateMapper.schema() + " where rate.is_active = true ";
return this.jdbcTemplate.query(sql, rateMapper);// NOSONAR
}
@Override
public List<FloatingRateData> retrieveLookupActive() {
FloatingRateLookupMapper rateMapper = new FloatingRateLookupMapper();
final String sql = "select " + rateMapper.schema() + " where rate.is_active = true ";
return this.jdbcTemplate.query(sql, rateMapper); // NOSONAR
}
@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 }); // NOSONAR
} catch (final EmptyResultDataAccessException e) {
throw new FloatingRateNotFoundException(floatingRateId, e);
}
}
@Override
public List<InterestRatePeriodData> retrieveInterestRatePeriods(final Long productId) {
try {
FloatingInterestRatePeriodRowMapper mapper = new FloatingInterestRatePeriodRowMapper();
return this.jdbcTemplate.query(mapper.schema(), mapper, new Object[] { productId }); // NOSONAR
} catch (final EmptyResultDataAccessException e) {
throw new FloatingRateNotFoundException("error.msg.floatingrate.not.found.for.product", e);
}
}
@Override
public FloatingRateData retrieveBaseLendingRate() {
try {
FloatingRateRowMapper rateMapper = new FloatingRateRowMapper(true);
final String sql = "select " + rateMapper.schema() + " where rate.is_base_lending_rate = true and rate.is_active = true";
return this.jdbcTemplate.queryForObject(sql, rateMapper); // NOSONAR
} catch (final EmptyResultDataAccessException e) {
throw new FloatingRateNotFoundException("error.msg.floatingrate.base.lending.rate.not.found", e);
}
}
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("rate.created_on_utc as createdOnUTC, ").append("moappu.username as modifiedBy, ")
.append("rate.lastmodified_date as modifiedOn, ").append("rate.last_modified_on_utc as modifiedOnUTC ")
.append("FROM m_floating_rates as rate ").append("LEFT JOIN m_appuser as crappu on rate.created_by = crappu.id ")
.append("LEFT JOIN m_appuser as moappu on rate.last_modified_by = moappu.id ");
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 OffsetDateTime createdOnLocal = JdbcSupport.getOffsetDateTime(rs, "createdOn");
final OffsetDateTime createdOnUtc = JdbcSupport.getOffsetDateTime(rs, "createdOnUTC");
final String modifiedBy = rs.getString("modifiedBy");
final OffsetDateTime modifiedOnLocal = JdbcSupport.getOffsetDateTime(rs, "modifiedOn");
final OffsetDateTime modifiedOnUtc = JdbcSupport.getOffsetDateTime(rs, "modifiedOnUTC");
List<FloatingRatePeriodData> ratePeriods = null;
final OffsetDateTime createdOn = createdOnUtc != null ? createdOnUtc : createdOnLocal;
final OffsetDateTime modifiedOn = modifiedOnUtc != null ? modifiedOnUtc : modifiedOnLocal;
if (addRatePeriods) {
FloatingRatePeriodRowMapper ratePeriodMapper = new FloatingRatePeriodRowMapper();
final String sql = "select " + ratePeriodMapper.schema()
+ " where period.is_active = true and period.floating_rates_id = ? " + " order by period.from_date desc ";
ratePeriods = jdbcTemplate.query(sql, ratePeriodMapper, id); // NOSONAR
}
return new FloatingRateData(id, name, isBaseLendingRate, isActive, createdBy, createdOn, modifiedBy, modifiedOn, ratePeriods,
null);
}
public String schema() {
return sqlQuery.toString();
}
}
private static 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("period.created_on_utc as createdOnUTC, ")
.append("moappu.username as modifiedBy, ").append("period.lastmodified_date as modifiedOn, ")
.append("period.last_modified_on_utc as modifiedOnUTC ").append("FROM m_floating_rates_periods as period ")
.append("LEFT JOIN m_appuser as crappu on period.created_by = crappu.id ")
.append("LEFT JOIN m_appuser as moappu on period.last_modified_by = moappu.id ");
@Override
public FloatingRatePeriodData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "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 OffsetDateTime createdOnLocal = JdbcSupport.getOffsetDateTime(rs, "createdOn");
final OffsetDateTime createdOnUtc = JdbcSupport.getOffsetDateTime(rs, "createdOnUTC");
final String modifiedBy = rs.getString("modifiedBy");
final OffsetDateTime modifiedOnLocal = JdbcSupport.getOffsetDateTime(rs, "modifiedOn");
final OffsetDateTime modifiedOnUtc = JdbcSupport.getOffsetDateTime(rs, "modifiedOnUTC");
final OffsetDateTime createdOn = createdOnUtc != null ? createdOnUtc : createdOnLocal;
final OffsetDateTime modifiedOn = modifiedOnUtc != null ? modifiedOnUtc : modifiedOnLocal;
return new FloatingRatePeriodData(id, fromDate, interestRate, isDifferentialToBaseLendingRate, isActive, createdBy, createdOn,
modifiedBy, modifiedOn);
}
public String schema() {
return sqlQuery.toString();
}
}
private static 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 static 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_product_loan as lp ")
.append(" join m_product_loan_floating_rates as plfr on lp.id = plfr.loan_product_id ")
.append(" join m_floating_rates as linkedrate on linkedrate.id = plfr.floating_rates_id ")
.append("left join m_floating_rates_periods as linkedrateperiods on (linkedrate.id = linkedrateperiods.floating_rates_id and linkedrateperiods.is_active = true) ")
.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 = true) ")
.append(" where blr.is_base_lending_rate = true and blr.is_active = true ")
.append(") as baserate on (linkedrateperiods.is_differential_to_base_lending_rate = true and linkedrate.is_base_lending_rate = false) ")
.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 = true) ")
.append(" where blr.is_base_lending_rate = true and blr.is_active = true ").append(" ) as b ")
.append(" where b.from_date <= linkedrateperiods.from_date)) ").append("and lp.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");
final LocalDate fromLocalDate = fromDate != null ? fromDate.toLocalDate() : null;
final LocalDate blrFromLocalDate = blrFromDate != null ? blrFromDate.toLocalDate() : null;
return new InterestRatePeriodData(fromLocalDate, interestRate, isDifferentialToBLR, blrFromLocalDate, blrInterestRate);
}
public String schema() {
return sqlQuery.toString();
}
}
}
@@ -0,0 +1,47 @@
/**
* 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.starter;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper;
import org.apache.fineract.portfolio.floatingrates.serialization.FloatingRateDataValidator;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRateWritePlatformService;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRateWritePlatformServiceImpl;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformServiceImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
@Configuration
public class FloatingRatesConfiguration {
@Bean
@ConditionalOnMissingBean(FloatingRatesReadPlatformService.class)
public FloatingRatesReadPlatformService floatingRatesReadPlatformService(JdbcTemplate jdbcTemplate) {
return new FloatingRatesReadPlatformServiceImpl(jdbcTemplate);
}
@Bean
@ConditionalOnMissingBean(FloatingRateWritePlatformService.class)
public FloatingRateWritePlatformService floatingRateWritePlatformService(FloatingRateDataValidator fromApiJsonDeserializer,
FloatingRateRepositoryWrapper floatingRateRepository) {
return new FloatingRateWritePlatformServiceImpl(fromApiJsonDeserializer, floatingRateRepository);
}
}
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!-- Sequence is starting from 5000 to make it easier to move existing liquibase changesets here -->
</databaseChangeLog>
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<!-- This file is only used for static weaving, nothing more. -->
<!-- You can find the runtime configuration in the JPAConfig class -->
<persistence-unit name="jpa-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<!-- Fineract core module -->
<class>org.apache.fineract.accounting.glaccount.domain.GLAccount</class>
<class>org.apache.fineract.accounting.journalentry.domain.JournalEntry</class>
<class>org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom</class>
<class>org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom</class>
<class>org.apache.fineract.infrastructure.codes.domain.Code</class>
<class>org.apache.fineract.infrastructure.codes.domain.CodeValue</class>
<class>org.apache.fineract.infrastructure.documentmanagement.domain.Image</class>
<class>org.apache.fineract.organisation.staff.domain.Staff</class>
<class>org.apache.fineract.organisation.office.domain.Office</class>
<class>org.apache.fineract.organisation.office.domain.OrganisationCurrency</class>
<class>org.apache.fineract.organisation.monetary.domain.ApplicationCurrency</class>
<class>org.apache.fineract.organisation.holiday.domain.Holiday</class>
<class>org.apache.fineract.organisation.workingdays.domain.WorkingDays</class>
<class>org.apache.fineract.portfolio.group.domain.Group</class>
<class>org.apache.fineract.portfolio.group.domain.GroupLevel</class>
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
<class>org.apache.fineract.portfolio.client.domain.Client</class>
<class>org.apache.fineract.portfolio.rate.domain.Rate</class>
<class>org.apache.fineract.portfolio.fund.domain.Fund</class>
<class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class>
<class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class>
<class>org.apache.fineract.portfolio.charge.domain.Charge</class>
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
<class>org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail</class>
<class>org.apache.fineract.portfolio.tax.domain.TaxGroup</class>
<class>org.apache.fineract.portfolio.tax.domain.TaxGroupMappings</class>
<class>org.apache.fineract.portfolio.tax.domain.TaxComponent</class>
<class>org.apache.fineract.portfolio.tax.domain.TaxComponentHistory</class>
<class>org.apache.fineract.portfolio.floatingrates.domain.FloatingRate</class>
<class>org.apache.fineract.portfolio.floatingrates.domain.FloatingRatePeriod</class>
<class>org.apache.fineract.portfolio.calendar.domain.Calendar</class>
<class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class>
<class>org.apache.fineract.portfolio.calendar.domain.CalendarInstance</class>
<class>org.apache.fineract.useradministration.domain.AppUser</class>
<class>org.apache.fineract.useradministration.domain.Role</class>
<class>org.apache.fineract.useradministration.domain.Permission</class>
<class>org.apache.fineract.useradministration.domain.AppUserClientMapping</class>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.weaving" value="static" />
</properties>
</persistence-unit>
</persistence>