Delinquency buckets management for Loans (#2442)

Co-authored-by: Jose Alberto Hernandez <alberto@MacBook-Pro.local>
This commit is contained in:
José Alberto Hernández
2022-08-08 14:16:09 -05:00
committed by GitHub
co-authored by Jose Alberto Hernandez
parent f78a596878
commit 9e949887b9
52 changed files with 2945 additions and 59 deletions
@@ -3494,4 +3494,50 @@ public class CommandWrapperBuilder {
return this;
}
public CommandWrapperBuilder createDelinquencyRange() {
this.actionName = "CREATE";
this.entityName = "DELINQUENCY_RANGE";
this.href = "/delinquency/range";
return this;
}
public CommandWrapperBuilder updateDelinquencyRange(final Long delinquencyRangeId) {
this.actionName = "UPDATE";
this.entityName = "DELINQUENCY_RANGE";
this.entityId = delinquencyRangeId;
this.href = "/delinquency/range";
return this;
}
public CommandWrapperBuilder deleteDelinquencyRange(final Long delinquencyRangeId) {
this.actionName = "DELETE";
this.entityName = "DELINQUENCY_RANGE";
this.entityId = delinquencyRangeId;
this.href = "/delinquency/range";
return this;
}
public CommandWrapperBuilder createDelinquencyBucket() {
this.actionName = "CREATE";
this.entityName = "DELINQUENCY_BUCKET";
this.href = "/delinquency/bucket";
return this;
}
public CommandWrapperBuilder updateDelinquencyBucket(final Long delinquencyBucketId) {
this.actionName = "UPDATE";
this.entityName = "DELINQUENCY_BUCKET";
this.entityId = delinquencyBucketId;
this.href = "/delinquency/bucket";
return this;
}
public CommandWrapperBuilder deleteDelinquencyBucket(final Long delinquencyBucketId) {
this.actionName = "DELETE";
this.entityName = "DELINQUENCY_BUCKET";
this.entityId = delinquencyBucketId;
this.href = "/delinquency/bucket";
return this;
}
}
@@ -0,0 +1,51 @@
/**
* 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.infrastructure.core.validator;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
@Slf4j
public class ParseAndValidator {
protected JsonObject extractJsonObject(final FromJsonHelper jsonHelper, final JsonCommand command) {
String json = command.json();
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final JsonElement element = jsonHelper.parse(json);
return element.getAsJsonObject();
}
protected void throwExceptionIfValidationWarningsExist(DataValidatorBuilder dataValidator) {
if (dataValidator.hasError()) {
log.error("Validation errors: {}", dataValidator.getDataValidationErrors());
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidator.getDataValidationErrors());
}
}
}
@@ -0,0 +1,34 @@
/**
* 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.delinquency.api;
public final class DelinquencyApiConstants {
private DelinquencyApiConstants() {
}
public static final String NAME_PARAM_NAME = "name";
public static final String CLASSIFICATION_PARAM_NAME = "classification";
public static final String RANGES_PARAM_NAME = "ranges";
public static final String MINIMUMAGEDAYS_PARAM_NAME = "minimumAgeDays";
public static final String MAXIMUMAGEDAYS_PARAM_NAME = "maximumAgeDays";
public static final String LOCALE_PARAM_NAME = "locale";
}
@@ -0,0 +1,235 @@
/**
* 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.delinquency.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 java.util.Collection;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriInfo;
import 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.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Path("delinquency")
@Component
@Tag(name = "Delinquency Range and Buckets Management", description = "Delinquency Range and Buckets management enables you to set up, fetch and adjust Delinquency overdue ranges")
public class DelinquencyApiResource {
private final ApiRequestParameterHelper parameterHelper;
private final PlatformSecurityContext securityContext;
private final DefaultToApiJsonSerializer<DelinquencyBucketData> jsonSerializerBucket;
private final DefaultToApiJsonSerializer<DelinquencyRangeData> jsonSerializerRange;
private final DelinquencyReadPlatformService readPlatformService;
private final PortfolioCommandSourceWritePlatformService commandWritePlatformService;
@GET
@Path("ranges")
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "List all Delinquency Ranges", description = "")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyRangesResponse.class)))) })
public String getDelinquencyRanges(@Context final UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasReadPermission("DELINQUENCY_BUCKET");
final Collection<DelinquencyRangeData> delinquencyRangeData = this.readPlatformService.retrieveAllDelinquencyRanges();
ApiRequestJsonSerializationSettings settings = parameterHelper.process(uriInfo.getQueryParameters());
return this.jsonSerializerRange.serialize(settings, delinquencyRangeData);
}
@GET
@Path("ranges/{delinquencyRangeId}")
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Retrieve a specific Delinquency Range based on the Id", description = "")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyRangesResponse.class))) })
public String getDelinquencyRange(
@PathParam("delinquencyRangeId") @Parameter(description = "delinquencyRangeId") final Long delinquencyRangeId,
@Context final UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasReadPermission("DELINQUENCY_BUCKET");
final DelinquencyRangeData delinquencyRangeData = this.readPlatformService.retrieveDelinquencyRange(delinquencyRangeId);
ApiRequestJsonSerializationSettings settings = parameterHelper.process(uriInfo.getQueryParameters());
return this.jsonSerializerRange.serialize(settings, delinquencyRangeData);
}
@POST
@Path("ranges")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create Delinquency Range", description = "")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostDelinquencyRangeRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostDelinquencyRangeResponse.class))) })
public String createDelinquencyRange(final String jsonRequestBody, @Context UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasCreatePermission("DELINQUENCY_BUCKET");
final CommandWrapper commandRequest = new CommandWrapperBuilder().createDelinquencyRange().withJson(jsonRequestBody).build();
CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest);
return jsonSerializerRange.serialize(result);
}
@PUT
@Path("ranges/{delinquencyRangeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update Delinquency Range based on the Id", description = "")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostDelinquencyRangeRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PutDelinquencyRangeResponse.class))) })
public String updateDelinquencyRange(
@PathParam("delinquencyRangeId") @Parameter(description = "delinquencyRangeId") final Long delinquencyRangeId,
final String jsonRequestBody, @Context UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasUpdatePermission("DELINQUENCY_BUCKET");
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDelinquencyRange(delinquencyRangeId)
.withJson(jsonRequestBody).build();
CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest);
return jsonSerializerRange.serialize(result);
}
@DELETE
@Path("ranges/{delinquencyRangeId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update Delinquency Range based on the Id", description = "")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostDelinquencyRangeRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.DeleteDelinquencyRangeResponse.class))) })
public String deleteDelinquencyRange(
@PathParam("delinquencyRangeId") @Parameter(description = "delinquencyRangeId") final Long delinquencyRangeId,
@Context UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasDeletePermission("DELINQUENCY_BUCKET");
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteDelinquencyRange(delinquencyRangeId).build();
CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest);
return jsonSerializerRange.serialize(result);
}
@GET
@Path("buckets")
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "List all Delinquency Buckets", description = "")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyBucketsResponse.class)))) })
public String getDelinquencyBuckets(@Context final UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasReadPermission("DELINQUENCY_BUCKET");
final Collection<DelinquencyBucketData> delinquencyBucketData = this.readPlatformService.retrieveAllDelinquencyBuckets();
ApiRequestJsonSerializationSettings settings = parameterHelper.process(uriInfo.getQueryParameters());
return this.jsonSerializerBucket.serialize(settings, delinquencyBucketData);
}
@GET
@Path("buckets/{delinquencyBucketId}")
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Retrieve a specific Delinquency Bucket based on the Id", description = "")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyBucketsResponse.class))) })
public String getDelinquencyBucket(
@PathParam("delinquencyBucketId") @Parameter(description = "delinquencyBucketId") final Long delinquencyBucketId,
@Context final UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasReadPermission("DELINQUENCY_BUCKET");
final DelinquencyBucketData delinquencyBucketData = this.readPlatformService.retrieveDelinquencyBucket(delinquencyBucketId);
ApiRequestJsonSerializationSettings settings = parameterHelper.process(uriInfo.getQueryParameters());
return this.jsonSerializerBucket.serialize(settings, delinquencyBucketData);
}
@POST
@Path("buckets")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Create Delinquency Bucket", description = "")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostDelinquencyBucketRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostDelinquencyBucketResponse.class))) })
public String createDelinquencyBucket(final String jsonRequestBody, @Context UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasCreatePermission("DELINQUENCY_BUCKET");
final CommandWrapper commandRequest = new CommandWrapperBuilder().createDelinquencyBucket().withJson(jsonRequestBody).build();
CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest);
return jsonSerializerRange.serialize(result);
}
@PUT
@Path("buckets/{delinquencyBucketId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Update Delinquency Bucket based on the Id", description = "")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostDelinquencyBucketRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PutDelinquencyBucketResponse.class))) })
public String updateDelinquencyBucket(
@PathParam("delinquencyBucketId") @Parameter(description = "delinquencyBucketId") final Long delinquencyBucketId,
final String jsonRequestBody, @Context UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasUpdatePermission("DELINQUENCY_BUCKET");
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateDelinquencyBucket(delinquencyBucketId)
.withJson(jsonRequestBody).build();
CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest);
return jsonSerializerRange.serialize(result);
}
@DELETE
@Path("buckets/{delinquencyBucketId}")
@Consumes({ MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_JSON })
@Operation(summary = "Delete Delinquency Bucket based on the Id", description = "")
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostDelinquencyBucketRequest.class)))
@ApiResponses({
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.DeleteDelinquencyBucketResponse.class))) })
public String deleteDelinquencyBucket(
@PathParam("delinquencyBucketId") @Parameter(description = "delinquencyBucketId") final Long delinquencyBucketId,
@Context UriInfo uriInfo) {
securityContext.authenticatedUser().validateHasDeletePermission("DELINQUENCY_BUCKET");
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteDelinquencyBucket(delinquencyBucketId).build();
CommandProcessingResult result = commandWritePlatformService.logCommandSource(commandRequest);
return jsonSerializerRange.serialize(result);
}
}
@@ -0,0 +1,136 @@
/**
* 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.delinquency.api;
import io.swagger.v3.oas.annotations.media.Schema;
public final class DelinquencyApiResourceSwagger {
private DelinquencyApiResourceSwagger() {}
@Schema(description = "GetDelinquencyRangesResponse")
public static final class GetDelinquencyRangesResponse {
private GetDelinquencyRangesResponse() {}
@Schema(example = "1")
public Integer id;
@Schema(example = "Delinquent 1")
public String classification;
@Schema(example = "1")
public Long minimumAgeDays;
@Schema(example = "3")
public Long maximumAgeDays;
}
@Schema(description = "GetDelinquencyBucketsResponse")
public static final class GetDelinquencyBucketsResponse {
private GetDelinquencyBucketsResponse() {}
@Schema(example = "1")
public Integer id;
@Schema(example = "Delinquent Bucket Set 1")
public String name;
public GetDelinquencyRangesResponse[] ranges;
}
@Schema(description = "PostDelinquencyRangeRequest")
public static final class PostDelinquencyRangeRequest {
private PostDelinquencyRangeRequest() {}
@Schema(example = "Delinquent 1")
public String classification;
@Schema(example = "1")
public Integer minimumAgeDays;
@Schema(example = "3")
public Integer maximumAgeDays;
@Schema(example = "en")
public String locale;
}
@Schema(description = "PostDelinquencyRangeResponse")
public static final class PostDelinquencyRangeResponse {
private PostDelinquencyRangeResponse() {}
@Schema(example = "1")
public Integer resourceId;
}
@Schema(description = "PutDelinquencyRangeResponse")
public static final class PutDelinquencyRangeResponse {
private PutDelinquencyRangeResponse() {}
@Schema(example = "1")
public Integer resourceId;
public PostDelinquencyRangeRequest changes;
}
@Schema(description = "DeleteDelinquencyRangeResponse")
public static final class DeleteDelinquencyRangeResponse {
private DeleteDelinquencyRangeResponse() {}
@Schema(example = "1")
public Integer resourceId;
}
@Schema(description = "PostDelinquencyBucketRequest")
public static final class PostDelinquencyBucketRequest {
private PostDelinquencyBucketRequest() {}
@Schema(example = "Delinquent 1")
public String name;
@Schema(example = "[1,2,3]")
public Long[] ranges;
}
@Schema(description = "PostDelinquencyBucketResponse")
public static final class PostDelinquencyBucketResponse {
private PostDelinquencyBucketResponse() {}
@Schema(example = "1")
public Integer resourceId;
}
@Schema(description = "PutDelinquencyBucketResponse")
public static final class PutDelinquencyBucketResponse {
private PutDelinquencyBucketResponse() {}
@Schema(example = "1")
public Integer resourceId;
}
@Schema(description = "DeleteDelinquencyBucketResponse")
public static final class DeleteDelinquencyBucketResponse {
private DeleteDelinquencyBucketResponse() {}
@Schema(example = "1")
public Integer resourceId;
}
}
@@ -0,0 +1,38 @@
/**
* 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.delinquency.data;
import java.io.Serializable;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@AllArgsConstructor
@Getter
@Setter
public class DelinquencyBucketData implements Serializable {
private Long id;
private String name;
private List<DelinquencyRangeData> ranges;
}
@@ -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.delinquency.data;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@AllArgsConstructor
@Getter
@Setter
public class DelinquencyRangeData implements Serializable {
private Long id;
private String classification;
private Integer minimumAgeDays;
private Integer maximumAgeDays;
public static DelinquencyRangeData instance(String classification, Integer minimumAgeDays, Integer maximumAgeDays) {
return new DelinquencyRangeData(null, classification, minimumAgeDays, maximumAgeDays);
}
public static DelinquencyRangeData reference(Long id) {
return new DelinquencyRangeData(id, "", 0, 0);
}
}
@@ -0,0 +1,58 @@
/**
* 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.delinquency.domain;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "m_delinquency_bucket", uniqueConstraints = {
@UniqueConstraint(name = "uq_delinquency_bucket_name", columnNames = { "name" }) })
public class DelinquencyBucket extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "name", nullable = false)
private String name;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "m_delinquency_bucket_mappings", joinColumns = @JoinColumn(name = "delinquency_bucket_id"), inverseJoinColumns = @JoinColumn(name = "delinquency_range_id"))
private Set<DelinquencyRange> ranges;
@Version
private Long version;
public DelinquencyBucket(String name) {
this.name = name;
}
}
@@ -0,0 +1,60 @@
/**
* 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.delinquency.domain;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "m_delinquency_bucket_mappings", uniqueConstraints = {
@UniqueConstraint(name = "uq_delinquency_bucket_mapping", columnNames = { "delinquencyBucket", "delinquencyRange" }) })
public class DelinquencyBucketMappings extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne
@JoinColumn(name = "delinquency_bucket_id", nullable = false)
private DelinquencyBucket delinquencyBucket;
@ManyToOne
@JoinColumn(name = "delinquency_range_id", nullable = false)
private DelinquencyRange delinquencyRange;
@Version
private Long version;
public DelinquencyBucketMappings(DelinquencyBucket delinquencyBucket, DelinquencyRange delinquencyRange) {
this.delinquencyBucket = delinquencyBucket;
this.delinquencyRange = delinquencyRange;
}
public static DelinquencyBucketMappings instance(DelinquencyBucket delinquencyBucket, DelinquencyRange delinquencyRange) {
return new DelinquencyBucketMappings(delinquencyBucket, delinquencyRange);
}
}
@@ -0,0 +1,30 @@
/**
* 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.delinquency.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface DelinquencyBucketMappingsRepository
extends JpaRepository<DelinquencyBucketMappings, Long>, JpaSpecificationExecutor<DelinquencyBucketMappings> {
List<DelinquencyBucketMappings> findByDelinquencyBucket(DelinquencyBucket delinquencyBucket);
}
@@ -0,0 +1,28 @@
/**
* 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.delinquency.domain;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface DelinquencyBucketRepository extends JpaRepository<DelinquencyBucket, Long>, JpaSpecificationExecutor<DelinquencyBucket> {
Optional<DelinquencyBucket> findByName(String name);
}
@@ -0,0 +1,62 @@
/**
* 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.delinquency.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "m_delinquency_range", uniqueConstraints = {
@UniqueConstraint(name = "uq_delinquency_range_classification", columnNames = { "classification" }) })
public class DelinquencyRange extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "classification", nullable = false)
private String classification;
@Column(name = "min_age_days", nullable = false)
private Integer minimumAgeDays;
@Column(name = "max_age_days", nullable = true)
private Integer maximumAgeDays;
@Version
private Long version;
protected DelinquencyRange(@NotNull String classification, @NotNull Integer minimumAgeDays, Integer maximumAgeDays) {
this.classification = classification;
this.minimumAgeDays = minimumAgeDays;
this.maximumAgeDays = maximumAgeDays;
}
public static DelinquencyRange instance(@NotNull String classification, @NotNull Integer minimumAge, Integer maximumAge) {
return new DelinquencyRange(classification, minimumAge, maximumAge);
}
}
@@ -0,0 +1,28 @@
/**
* 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.delinquency.domain;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface DelinquencyRangeRepository extends JpaRepository<DelinquencyRange, Long>, JpaSpecificationExecutor<DelinquencyRange> {
Optional<DelinquencyRange> findByClassification(String classification);
}
@@ -0,0 +1,66 @@
/**
* 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.delinquency.domain;
import java.time.LocalDate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Version;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@Getter
@Setter
@NoArgsConstructor
@Entity
@Table(name = "m_loan_delinquency_tag_history")
public class LoanDelinquencyTagHistory extends AbstractAuditableWithUTCDateTimeCustom {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "delinquency_range_id", nullable = false)
private DelinquencyRange delinquencyRange;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@Column(name = "addedon_date", nullable = false)
private LocalDate addedOnDate;
@Column(name = "liftedon_date", nullable = true)
private LocalDate liftedOnDate;
@Version
private Long version;
public LoanDelinquencyTagHistory(DelinquencyRange delinquencyRange, Loan loan, LocalDate addedOnDate, LocalDate liftedOnDate) {
this.delinquencyRange = delinquencyRange;
this.loan = loan;
this.addedOnDate = addedOnDate;
this.liftedOnDate = liftedOnDate;
}
}
@@ -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.delinquency.domain;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface LoanDelinquencyTagHistoryRepository
extends JpaRepository<LoanDelinquencyTagHistory, Long>, JpaSpecificationExecutor<LoanDelinquencyTagHistory> {
List<LoanDelinquencyTagHistory> findByLoanOrderByAddedOnDateDesc(Loan loan);
Optional<LoanDelinquencyTagHistory> findByLoanAndLiftedOnDate(Loan loan, LocalDate liftedOnDate);
Long countByDelinquencyRangeAndLiftedOnDate(DelinquencyRange delinquencyRange, LocalDate liftedOnDate);
Long countByDelinquencyRange(DelinquencyRange delinquencyRange);
}
@@ -0,0 +1,28 @@
/**
* 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.delinquency.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class DelinquencyBucketAgesOverlapedException extends AbstractPlatformDomainRuleException {
public DelinquencyBucketAgesOverlapedException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.loan.delinquency.bucket.ages.overlaped", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -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.delinquency.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class DelinquencyBucketNotFoundException extends AbstractPlatformResourceNotFoundException {
public DelinquencyBucketNotFoundException(String globalisationMessageCode, String defaultUserMessage,
Object... defaultUserMessageArgs) {
super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
}
public static DelinquencyBucketNotFoundException notExist(final Long id, Throwable... e) {
return new DelinquencyBucketNotFoundException("error.msg.delinquency.bucket.id.not.exist",
"Delinquency bucket with id `" + id + "` does not exist.", id, e);
}
public static DelinquencyBucketNotFoundException notFound(final Long id, Throwable... e) {
return new DelinquencyBucketNotFoundException("error.msg.delinquency.bucket.not.found",
"Delinquency bucket with id `" + id + "` does not found.", id, e);
}
}
@@ -0,0 +1,28 @@
/**
* 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.delinquency.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class DelinquencyRangeInvalidAgesException extends AbstractPlatformDomainRuleException {
public DelinquencyRangeInvalidAgesException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.loan.delinquency.age.days,invalid", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,38 @@
/**
* 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.delinquency.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class DelinquencyRangeNotFoundException extends AbstractPlatformResourceNotFoundException {
public DelinquencyRangeNotFoundException(String globalisationMessageCode, String defaultUserMessage, Object... defaultUserMessageArgs) {
super(globalisationMessageCode, defaultUserMessage, defaultUserMessageArgs);
}
public static DelinquencyRangeNotFoundException notExist(final Long id, Throwable... e) {
return new DelinquencyRangeNotFoundException("error.msg.delinquency.range.id.not.exist",
"Delinquency range with id `" + id + "` does not exist.", id, e);
}
public static DelinquencyRangeNotFoundException notFound(final Long id, Throwable... e) {
return new DelinquencyRangeNotFoundException("error.msg.delinquency.range.not.found",
"Delinquency range with id `" + id + "` does not found.", id, e);
}
}
@@ -0,0 +1,42 @@
/**
* 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.delinquency.handler;
import lombok.RequiredArgsConstructor;
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.delinquency.service.DelinquencyWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@CommandType(entity = "DELINQUENCY_BUCKET", action = "CREATE")
public class CreateDelinquencyBucketCommandHandler implements NewCommandSourceHandler {
private final DelinquencyWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.createDelinquencyBucket(command);
}
}
@@ -0,0 +1,42 @@
/**
* 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.delinquency.handler;
import lombok.RequiredArgsConstructor;
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.delinquency.service.DelinquencyWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@CommandType(entity = "DELINQUENCY_RANGE", action = "CREATE")
public class CreateDelinquencyRangeCommandHandler implements NewCommandSourceHandler {
private final DelinquencyWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.createDelinquencyRange(command);
}
}
@@ -0,0 +1,42 @@
/**
* 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.delinquency.handler;
import lombok.RequiredArgsConstructor;
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.delinquency.service.DelinquencyWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@CommandType(entity = "DELINQUENCY_BUCKET", action = "DELETE")
public class DeleteDelinquencyBucketCommandHandler implements NewCommandSourceHandler {
private final DelinquencyWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.deleteDelinquencyBucket(command.entityId(), command);
}
}
@@ -0,0 +1,42 @@
/**
* 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.delinquency.handler;
import lombok.RequiredArgsConstructor;
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.delinquency.service.DelinquencyWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@CommandType(entity = "DELINQUENCY_RANGE", action = "DELETE")
public class DeleteDelinquencyRangeCommandHandler implements NewCommandSourceHandler {
private final DelinquencyWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.deleteDelinquencyRange(command.entityId(), command);
}
}
@@ -0,0 +1,42 @@
/**
* 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.delinquency.handler;
import lombok.RequiredArgsConstructor;
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.delinquency.service.DelinquencyWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@CommandType(entity = "DELINQUENCY_BUCKET", action = "UPDATE")
public class UpdateDelinquencyBucketCommandHandler implements NewCommandSourceHandler {
private final DelinquencyWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.updateDelinquencyBucket(command.entityId(), command);
}
}
@@ -0,0 +1,42 @@
/**
* 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.delinquency.handler;
import lombok.RequiredArgsConstructor;
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.delinquency.service.DelinquencyWritePlatformService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@CommandType(entity = "DELINQUENCY_RANGE", action = "UPDATE")
public class UpdateDelinquencyRangeCommandHandler implements NewCommandSourceHandler {
private final DelinquencyWritePlatformService writePlatformService;
@Transactional
@Override
public CommandProcessingResult processCommand(final JsonCommand command) {
return this.writePlatformService.updateDelinquencyRange(command.entityId(), command);
}
}
@@ -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.delinquency.mapper;
import java.util.List;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@Mapper(componentModel = "spring")
public interface DelinquencyBucketMapper {
@Mappings({ @Mapping(target = "id", source = "source.id"), @Mapping(target = "name", source = "source.name") })
DelinquencyBucketData map(DelinquencyBucket source);
List<DelinquencyBucketData> map(List<DelinquencyBucket> sources);
}
@@ -0,0 +1,38 @@
/**
* 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.delinquency.mapper;
import java.util.List;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@Mapper(componentModel = "spring")
public interface DelinquencyRangeMapper {
@Mappings({ @Mapping(target = "id", source = "source.id"), @Mapping(target = "classification", source = "source.classification"),
@Mapping(target = "minimumAgeDays", source = "source.minimumAgeDays"),
@Mapping(target = "maximumAgeDays", source = "source.maximumAgeDays") })
DelinquencyRangeData map(DelinquencyRange source);
List<DelinquencyRangeData> map(List<DelinquencyRange> sources);
}
@@ -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.delinquency.service;
import java.util.Collection;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
public interface DelinquencyReadPlatformService {
Collection<DelinquencyRangeData> retrieveAllDelinquencyRanges();
DelinquencyRangeData retrieveDelinquencyRange(Long delinquencyRangeId);
Collection<DelinquencyBucketData> retrieveAllDelinquencyBuckets();
DelinquencyBucketData retrieveDelinquencyBucket(Long delinquencyBucketId);
DelinquencyRangeData retrieveCurrentDelinquencyTag(Long loanId);
}
@@ -0,0 +1,125 @@
/**
* 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.delinquency.service;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper;
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final DelinquencyRangeRepository repositoryRange;
private final DelinquencyBucketRepository repositoryBucket;
private final LoanDelinquencyTagHistoryRepository repositoryLoanDelinquencyTagHistory;
private final DelinquencyRangeMapper mapperRange;
private final DelinquencyBucketMapper mapperBucket;
private final LoanAssembler loanAssembler;
@Override
public Collection<DelinquencyRangeData> retrieveAllDelinquencyRanges() {
final List<DelinquencyRange> delinquencyRangeList = repositoryRange.findAll();
return mapperRange.map(delinquencyRangeList);
}
@Override
public DelinquencyRangeData retrieveDelinquencyRange(Long delinquencyRangeId) {
DelinquencyRange delinquencyRangeList = repositoryRange.getReferenceById(delinquencyRangeId);
return mapperRange.map(delinquencyRangeList);
}
@Override
public Collection<DelinquencyBucketData> retrieveAllDelinquencyBuckets() {
final List<DelinquencyBucket> delinquencyRangeList = repositoryBucket.findAll();
return mapperBucket.map(delinquencyRangeList);
}
@Override
public DelinquencyBucketData retrieveDelinquencyBucket(Long delinquencyBucketId) {
try {
final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId);
final DelinquencyBucketData delinquencyBucketData = mapperBucket.map(delinquencyBucket);
final DelinquencyBucketMappingMapper rmMappings = new DelinquencyBucketMappingMapper();
final String sql = "select " + rmMappings.schema() + " where buc.id = ? order by dran.min_age_days";
List<DelinquencyRangeData> ranges = this.jdbcTemplate.query(sql, rmMappings, delinquencyBucketId); // NOSONAR
delinquencyBucketData.setRanges(ranges);
return delinquencyBucketData;
} catch (final EmptyResultDataAccessException e) {
throw DelinquencyBucketNotFoundException.notFound(delinquencyBucketId, e);
}
}
@Override
public DelinquencyRangeData retrieveCurrentDelinquencyTag(Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
Optional<LoanDelinquencyTagHistory> optLoanDelinquencyTag = this.repositoryLoanDelinquencyTagHistory.findByLoanAndLiftedOnDate(loan,
null);
if (optLoanDelinquencyTag.isPresent()) {
return mapperRange.map(optLoanDelinquencyTag.get().getDelinquencyRange());
}
return null;
}
private static final class DelinquencyBucketMappingMapper implements RowMapper<DelinquencyRangeData> {
public String schema() {
return " dran.id as id, dran.classification as classification, dran.min_age_days as minimumAgeDays, dran.max_age_days as maximumAgeDays from m_delinquency_bucket buc "
+ " inner join m_delinquency_bucket_mappings dmap on dmap.delinquency_bucket_id = buc.id "
+ " inner join m_delinquency_range dran on dran.id = dmap.delinquency_range_id ";
}
@Override
public DelinquencyRangeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String classification = rs.getString("classification");
final Integer minimumAgeDays = rs.getInt("minimumAgeDays");
final Integer maximumAgeDays = rs.getInt("maximumAgeDays");
return new DelinquencyRangeData(id, classification, minimumAgeDays, maximumAgeDays);
}
}
}
@@ -0,0 +1,40 @@
/**
* 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.delinquency.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface DelinquencyWritePlatformService {
CommandProcessingResult createDelinquencyRange(JsonCommand command);
CommandProcessingResult updateDelinquencyRange(Long delinquencyRangeId, JsonCommand command);
CommandProcessingResult deleteDelinquencyRange(Long delinquencyRangeId, JsonCommand command);
CommandProcessingResult setLoanDelinquencyTag(Long loanId, Long delinquencyRangeId, JsonCommand command);
CommandProcessingResult createDelinquencyBucket(JsonCommand command);
CommandProcessingResult updateDelinquencyBucket(Long delinquencyBucketId, JsonCommand command);
CommandProcessingResult deleteDelinquencyBucket(Long delinquencyBucketId, JsonCommand command);
}
@@ -0,0 +1,278 @@
/**
* 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.delinquency.service;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
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.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappings;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory;
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketAgesOverlapedException;
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeInvalidAgesException;
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlatformService {
private final DelinquencyBucketParseAndValidator dataValidatorBucket;
private final DelinquencyRangeParseAndValidator dataValidatorRange;
private final DelinquencyRangeRepository repositoryRange;
private final DelinquencyBucketRepository repositoryBucket;
private final DelinquencyBucketMappingsRepository repositoryBucketMappings;
private final LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository;
private final LoanRepositoryWrapper loanRepository;
private final LoanProductRepository loanProductRepository;
@Override
public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
Map<String, Object> changes = new HashMap<>();
DelinquencyRange delinquencyRange = createDelinquencyRange(data, changes);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
.build();
}
@Override
public CommandProcessingResult updateDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
DelinquencyRangeData data = dataValidatorRange.validateAndParseUpdate(command);
DelinquencyRange delinquencyRange = this.repositoryRange.getReferenceById(delinquencyRangeId);
Map<String, Object> changes = new HashMap<>();
delinquencyRange = updateDelinquencyRange(delinquencyRange, data, changes);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).with(changes)
.build();
}
@Override
public CommandProcessingResult deleteDelinquencyRange(Long delinquencyRangeId, JsonCommand command) {
final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
if (delinquencyRange != null) {
final Long delinquencyRangeLinked = this.loanDelinquencyTagRepository.countByDelinquencyRange(delinquencyRange);
if (delinquencyRangeLinked > 0) {
throw new PlatformDataIntegrityException("error.msg.data.integrity.issue.entity.linked",
"Data integrity issue with resource: " + delinquencyRange.getId());
}
repositoryRange.delete(delinquencyRange);
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyRange.getId()).build();
}
@Override
public CommandProcessingResult setLoanDelinquencyTag(Long loanId, Long delinquencyRangeId, JsonCommand command) {
setLoanDelinquencyTag(loanId, delinquencyRangeId);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loanId).build();
}
@Override
public CommandProcessingResult createDelinquencyBucket(JsonCommand command) {
DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
Map<String, Object> changes = new HashMap<>();
DelinquencyBucket delinquencyBucket = createDelinquencyBucket(data, changes);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
.build();
}
@Override
public CommandProcessingResult updateDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
DelinquencyBucketData data = dataValidatorBucket.validateAndParseUpdate(command);
DelinquencyBucket delinquencyBucket = this.repositoryBucket.getReferenceById(delinquencyBucketId);
Map<String, Object> changes = new HashMap<>();
delinquencyBucket = updateDelinquencyBucket(delinquencyBucket, data, changes);
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).with(changes)
.build();
}
@Override
public CommandProcessingResult deleteDelinquencyBucket(Long delinquencyBucketId, JsonCommand command) {
final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId);
if (delinquencyBucket != null) {
Long delinquencyBucketLinked = this.loanProductRepository.countByDelinquencyBucket(delinquencyBucket);
if (delinquencyBucketLinked > 0) {
throw new PlatformDataIntegrityException("error.msg.data.integrity.issue.entity.linked",
"Data integrity issue with resource: " + delinquencyBucket.getId());
}
repositoryBucket.delete(delinquencyBucket);
}
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).build();
}
private DelinquencyRange createDelinquencyRange(DelinquencyRangeData data, Map<String, Object> changes) {
Optional<DelinquencyRange> delinquencyRange = repositoryRange.findByClassification(data.getClassification());
if (delinquencyRange.isEmpty()) {
if (data.getMinimumAgeDays() > data.getMaximumAgeDays()) {
final String errorMessage = "The age days values are invalid, the maximum age days can't be lower than minimum age days";
throw new DelinquencyRangeInvalidAgesException(errorMessage, data.getMinimumAgeDays(), data.getMaximumAgeDays());
}
DelinquencyRange newDelinquencyRange = DelinquencyRange.instance(data.getClassification(), data.getMinimumAgeDays(),
data.getMaximumAgeDays());
return repositoryRange.saveAndFlush(newDelinquencyRange);
} else {
throw new PlatformDataIntegrityException("error.msg.data.integrity.issue.entity.duplicated",
"Data integrity issue with resource: " + delinquencyRange.get().getId());
}
}
private DelinquencyRange updateDelinquencyRange(DelinquencyRange delinquencyRange, DelinquencyRangeData data,
Map<String, Object> changes) {
if (!data.getClassification().equalsIgnoreCase(delinquencyRange.getClassification())) {
delinquencyRange.setClassification(data.getClassification());
changes.put(DelinquencyApiConstants.CLASSIFICATION_PARAM_NAME, data.getClassification());
}
if (!data.getMinimumAgeDays().equals(delinquencyRange.getMinimumAgeDays())) {
delinquencyRange.setMinimumAgeDays(data.getMinimumAgeDays());
changes.put(DelinquencyApiConstants.MINIMUMAGEDAYS_PARAM_NAME, data.getMinimumAgeDays());
}
if (!data.getMaximumAgeDays().equals(delinquencyRange.getMaximumAgeDays())) {
delinquencyRange.setMaximumAgeDays(data.getMaximumAgeDays());
changes.put(DelinquencyApiConstants.MAXIMUMAGEDAYS_PARAM_NAME, data.getMaximumAgeDays());
}
if (!changes.isEmpty()) {
delinquencyRange = repositoryRange.saveAndFlush(delinquencyRange);
}
return delinquencyRange;
}
private DelinquencyBucket createDelinquencyBucket(DelinquencyBucketData data, Map<String, Object> changes) {
Optional<DelinquencyBucket> delinquencyBucket = repositoryBucket.findByName(data.getName());
if (delinquencyBucket.isEmpty()) {
DelinquencyBucket newDelinquencyBucket = new DelinquencyBucket(data.getName());
repositoryBucket.save(newDelinquencyBucket);
setDelinquencyBucketMappings(newDelinquencyBucket, data);
return newDelinquencyBucket;
} else {
throw new PlatformDataIntegrityException("error.msg.data.integrity.issue.entity.duplicated",
"Data integrity issue with resource: " + delinquencyBucket.get().getId());
}
}
private DelinquencyBucket updateDelinquencyBucket(DelinquencyBucket delinquencyBucket, DelinquencyBucketData data,
Map<String, Object> changes) {
if (!data.getName().equalsIgnoreCase(delinquencyBucket.getName())) {
delinquencyBucket.setName(data.getName());
changes.put(DelinquencyApiConstants.NAME_PARAM_NAME, data.getName());
}
if (!changes.isEmpty()) {
delinquencyBucket = repositoryBucket.save(delinquencyBucket);
}
setDelinquencyBucketMappings(delinquencyBucket, data);
return delinquencyBucket;
}
private void setDelinquencyBucketMappings(DelinquencyBucket delinquencyBucket, DelinquencyBucketData data) {
List<Long> rangeIds = new ArrayList<>();
data.getRanges().forEach(dataRange -> {
rangeIds.add(dataRange.getId());
});
List<DelinquencyRange> ranges = repositoryRange.findAllById(rangeIds);
validateDelinquencyRanges(ranges);
List<DelinquencyBucketMappings> bucketMappings = repositoryBucketMappings.findByDelinquencyBucket(delinquencyBucket);
repositoryBucketMappings.deleteAll(bucketMappings);
bucketMappings.clear();
for (DelinquencyRange delinquencyRange : ranges) {
bucketMappings.add(DelinquencyBucketMappings.instance(delinquencyBucket, delinquencyRange));
}
repositoryBucketMappings.saveAllAndFlush(bucketMappings);
}
private void validateDelinquencyRanges(List<DelinquencyRange> ranges) {
// Sort the ranges based on the minAgeDays
final Comparator<DelinquencyRange> orderByMinAge = new Comparator<DelinquencyRange>() {
@Override
public int compare(DelinquencyRange o1, DelinquencyRange o2) {
return o1.getMinimumAgeDays().compareTo(o2.getMinimumAgeDays());
}
};
Collections.sort(ranges, orderByMinAge);
DelinquencyRange prevDelinquencyRange = null;
for (DelinquencyRange delinquencyRange : ranges) {
if (prevDelinquencyRange != null) {
if (isOverlaped(prevDelinquencyRange, delinquencyRange)) {
final String errorMessage = "The delinquency ranges age days values are overlaped";
throw new DelinquencyBucketAgesOverlapedException(errorMessage, prevDelinquencyRange, delinquencyRange);
}
}
prevDelinquencyRange = delinquencyRange;
}
}
private boolean isOverlaped(DelinquencyRange o1, DelinquencyRange o2) {
return Math.max(o1.getMinimumAgeDays(), o2.getMinimumAgeDays()) <= Math.min(o1.getMaximumAgeDays(), o2.getMaximumAgeDays());
}
public void setLoanDelinquencyTag(Long loanId, Long delinquencyRangeId) {
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
List<LoanDelinquencyTagHistory> loanDelinquencyTagHistory = this.loanDelinquencyTagRepository
.findByLoanOrderByAddedOnDateDesc(loan);
// The delinquencyRangeId in null means just goes out from Delinquency
if (delinquencyRangeId == null) {
// The Loan will go out from Delinquency
if (!loanDelinquencyTagHistory.isEmpty()) {
LoanDelinquencyTagHistory loanDelinquencyTagPrev = loanDelinquencyTagHistory.get(0);
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
this.loanDelinquencyTagRepository.saveAndFlush(loanDelinquencyTagPrev);
}
} else {
// The previous Loan Delinquency Tag will set as Lifted
if (!loanDelinquencyTagHistory.isEmpty()) {
LoanDelinquencyTagHistory loanDelinquencyTagPrev = loanDelinquencyTagHistory.get(0);
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
this.loanDelinquencyTagRepository.save(loanDelinquencyTagPrev);
}
final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
LoanDelinquencyTagHistory loanDelinquencyTag = new LoanDelinquencyTagHistory(delinquencyRange, loan, transactionDate, null);
loanDelinquencyTagHistory.add(loanDelinquencyTag);
this.loanDelinquencyTagRepository.saveAndFlush(loanDelinquencyTag);
}
}
}
@@ -0,0 +1,76 @@
/**
* 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.delinquency.validator;
import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.validator.ParseAndValidator;
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Component
public class DelinquencyBucketParseAndValidator extends ParseAndValidator {
private final FromJsonHelper jsonHelper;
public DelinquencyBucketData validateAndParseUpdate(@NotNull final JsonCommand command) {
final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("delinquencyBucket.create");
JsonObject element = extractJsonObject(jsonHelper, command);
DelinquencyBucketData result = validateAndParseUpdate(dataValidator, element, jsonHelper);
throwExceptionIfValidationWarningsExist(dataValidator);
return result;
}
private DelinquencyBucketData validateAndParseUpdate(final DataValidatorBuilder dataValidator, JsonObject element,
FromJsonHelper jsonHelper) {
if (element == null) {
return null;
}
jsonHelper.checkForUnsupportedParameters(element,
List.of(DelinquencyApiConstants.NAME_PARAM_NAME, DelinquencyApiConstants.RANGES_PARAM_NAME));
final String name = jsonHelper.extractStringNamed(DelinquencyApiConstants.NAME_PARAM_NAME, element);
dataValidator.reset().parameter(DelinquencyApiConstants.NAME_PARAM_NAME).value(name).notBlank();
final String[] rangeIds = jsonHelper.extractArrayNamed(DelinquencyApiConstants.RANGES_PARAM_NAME, element);
dataValidator.reset().parameter(DelinquencyApiConstants.RANGES_PARAM_NAME).value(rangeIds).notNull().arrayNotEmpty();
ArrayList<DelinquencyRangeData> ranges = new ArrayList<>();
if (rangeIds != null) {
for (String rangeId : rangeIds) {
ranges.add(DelinquencyRangeData.reference(Long.parseLong(rangeId)));
}
}
return dataValidator.hasError() ? null : new DelinquencyBucketData(null, name, ranges);
}
}
@@ -0,0 +1,77 @@
/**
* 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.delinquency.validator;
import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.validator.ParseAndValidator;
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.springframework.stereotype.Component;
@RequiredArgsConstructor
@Component
public class DelinquencyRangeParseAndValidator extends ParseAndValidator {
private final FromJsonHelper jsonHelper;
public DelinquencyRangeData validateAndParseUpdate(@NotNull final JsonCommand command) {
final DataValidatorBuilder dataValidator = new DataValidatorBuilder(new ArrayList<>()).resource("delinquencyrange.create");
JsonObject element = extractJsonObject(jsonHelper, command);
DelinquencyRangeData result = validateAndParseUpdate(dataValidator, element, jsonHelper);
throwExceptionIfValidationWarningsExist(dataValidator);
return result;
}
private DelinquencyRangeData validateAndParseUpdate(final DataValidatorBuilder dataValidator, JsonObject element,
FromJsonHelper jsonHelper) {
if (element == null) {
return null;
}
jsonHelper.checkForUnsupportedParameters(element,
List.of(DelinquencyApiConstants.CLASSIFICATION_PARAM_NAME, DelinquencyApiConstants.MINIMUMAGEDAYS_PARAM_NAME,
DelinquencyApiConstants.MAXIMUMAGEDAYS_PARAM_NAME, DelinquencyApiConstants.LOCALE_PARAM_NAME));
final String localeValue = jsonHelper.extractStringNamed(DelinquencyApiConstants.LOCALE_PARAM_NAME, element);
dataValidator.reset().parameter(DelinquencyApiConstants.LOCALE_PARAM_NAME).value(localeValue).notBlank();
final Locale locale = jsonHelper.extractLocaleParameter(element);
final String classification = jsonHelper.extractStringNamed(DelinquencyApiConstants.CLASSIFICATION_PARAM_NAME, element);
final Integer minimumAge = jsonHelper.extractIntegerNamed(DelinquencyApiConstants.MINIMUMAGEDAYS_PARAM_NAME, element, locale);
final Integer maximumAge = jsonHelper.extractIntegerNamed(DelinquencyApiConstants.MAXIMUMAGEDAYS_PARAM_NAME, element, locale);
dataValidator.reset().parameter(DelinquencyApiConstants.CLASSIFICATION_PARAM_NAME).value(classification).notBlank();
dataValidator.reset().parameter(DelinquencyApiConstants.MINIMUMAGEDAYS_PARAM_NAME).value(minimumAge).notBlank()
.integerGreaterThanNumber(0);
dataValidator.reset().parameter(DelinquencyApiConstants.MAXIMUMAGEDAYS_PARAM_NAME).value(maximumAge).ignoreIfNull()
.integerGreaterThanNumber(0);
return dataValidator.hasError() ? null : DelinquencyRangeData.instance(classification, minimumAge, maximumAge);
}
}
@@ -38,6 +38,7 @@ import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData;
import org.apache.fineract.portfolio.calendar.data.CalendarData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.group.data.GroupGeneralData;
@@ -239,6 +240,7 @@ public final class LoanAccountData {
private LocalDate expectedDisbursementDate;
private final CollectionData delinquent;
private final DelinquencyRangeData delinquencyRange;
public static LoanAccountData importInstanceIndividual(EnumOptionData loanTypeEnumOption, Long clientId, Long productId,
Long loanOfficerId, LocalDate submittedOnDate, Long fundId, BigDecimal principal, Integer numberOfRepayments,
@@ -426,6 +428,7 @@ public final class LoanAccountData {
this.isRatesEnabled = false;
this.fixedPrincipalPercentagePerInstallment = null;
this.delinquent = null;
this.delinquencyRange = null;
}
public Integer getRowIndex() {
@@ -592,6 +595,7 @@ public final class LoanAccountData {
final Boolean isRatesEnabled = false;
final BigDecimal fixedPrincipalPercentagePerInstallment = null;
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
@@ -613,7 +617,7 @@ public final class LoanAccountData {
interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup,
closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquent);
fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange);
}
/**
@@ -738,6 +742,7 @@ public final class LoanAccountData {
final Boolean isRatesEnabled = false;
final BigDecimal fixedPrincipalPercentagePerInstallment = null;
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
@@ -759,7 +764,7 @@ public final class LoanAccountData {
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup,
closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquent);
fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange);
}
public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) {
@@ -790,7 +795,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange);
}
/**
@@ -917,6 +922,7 @@ public final class LoanAccountData {
final Boolean isRatesEnabled = false;
final BigDecimal fixedPrincipalPercentagePerInstallment = null;
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName,
@@ -938,7 +944,7 @@ public final class LoanAccountData {
originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods,
isVariableInstallmentsAllowed, minimumGap, maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup,
closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment, delinquent);
fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange);
}
public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) {
@@ -968,7 +974,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange);
}
public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product,
@@ -1110,6 +1116,7 @@ public final class LoanAccountData {
final List<RateData> rates = null;
final Boolean isRatesEnabled = false;
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group,
loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(),
@@ -1136,7 +1143,7 @@ public final class LoanAccountData {
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
product.getMaximumGapBetweenInstallments(), subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId,
closureLoanAccountNo, topupAmount, product.isEqualAmortization(), rates, isRatesEnabled,
product.getFixedPrincipalPercentagePerInstallment(), delinquent);
product.getFixedPrincipalPercentagePerInstallment(), delinquent, delinquencyRange);
}
public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) {
@@ -1179,6 +1186,7 @@ public final class LoanAccountData {
}
}
final CollectionData delinquent = CollectionData.template();
final DelinquencyRangeData delinquencyRange = null;
return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName,
acc.clientOfficeId, acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(),
@@ -1206,7 +1214,7 @@ public final class LoanAccountData {
product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(),
product.getMaximumGapBetweenInstallments(), acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization(), acc.rates, acc.isRatesEnabled,
product.getFixedPrincipalPercentagePerInstallment(), delinquent);
product.getFixedPrincipalPercentagePerInstallment(), delinquent, delinquencyRange);
}
/*
@@ -1237,7 +1245,8 @@ public final class LoanAccountData {
final LoanInterestRecalculationData interestRecalculationData, final Boolean createStandingInstructionAtDisbursement,
final Boolean isVariableInstallmentsAllowed, Integer minimumGap, Integer maximumGap, final EnumOptionData subStatus,
final boolean canUseForTopup, final boolean isTopup, final Long closureLoanId, final String closureLoanAccountNo,
final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment) {
final BigDecimal topupAmount, final boolean isEqualAmortization, final BigDecimal fixedPrincipalPercentagePerInstallment,
final DelinquencyRangeData delinquencyRange) {
final LoanScheduleData repaymentSchedule = null;
final Collection<LoanTransactionData> transactions = null;
@@ -1297,7 +1306,7 @@ public final class LoanAccountData {
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule,
createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap,
maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount,
isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent);
isEqualAmortization, rates, isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquent, delinquencyRange);
}
/*
@@ -1351,7 +1360,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, clientActiveLoanOptions, acc.isTopup, acc.closureLoanId,
acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, rates, isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, delinquent);
acc.fixedPrincipalPercentagePerInstallment, delinquent, acc.delinquencyRange);
}
public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection<LoanProductData> productOptions,
@@ -1394,7 +1403,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange);
}
public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map<Long, Integer> memberLoanCycle) {
@@ -1460,7 +1469,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange);
}
public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData,
@@ -1494,7 +1503,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange);
}
public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) {
@@ -1523,7 +1532,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange);
}
public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) {
@@ -1553,7 +1562,7 @@ public final class LoanAccountData {
acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed,
acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, acc.clientActiveLoanOptions, acc.isTopup,
acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, acc.isRatesEnabled,
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent);
acc.fixedPrincipalPercentagePerInstallment, acc.delinquent, acc.delinquencyRange);
}
private LoanAccountData(final Long id, //
@@ -1606,7 +1615,8 @@ public final class LoanAccountData {
final Integer minimumGap, final Integer maximumGap, final EnumOptionData subStatus, final Boolean canUseForTopup,
final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final boolean isTopup, final Long closureLoanId,
final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization, final List<RateData> rates,
final Boolean isRatesEnabled, final BigDecimal fixedPrincipalPercentagePerInstallment, final CollectionData delinquent) {
final Boolean isRatesEnabled, final BigDecimal fixedPrincipalPercentagePerInstallment, final CollectionData delinquent,
final DelinquencyRangeData delinquencyRange) {
this.id = id;
this.accountNo = accountNo;
@@ -1794,6 +1804,7 @@ public final class LoanAccountData {
this.rates = rates;
this.fixedPrincipalPercentagePerInstallment = fixedPrincipalPercentagePerInstallment;
this.delinquent = delinquent;
this.delinquencyRange = delinquencyRange;
}
public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() {
@@ -151,7 +151,7 @@ public class RecalculateInterestForLoanTasklet implements Tasklet {
List<Callable<Void>> posters = new ArrayList<>();
int fromIndex = 0;
int size = loanIds.size();
double toGetCeilValue = size / threadPoolSize;
double toGetCeilValue = size / ((double) threadPoolSize);
batchSize = (int) Math.ceil(toGetCeilValue);
if (batchSize == 0) {
@@ -70,6 +70,8 @@ import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.fund.data.FundData;
@@ -157,6 +159,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final AccountDetailsReadPlatformService accountDetailsReadPlatformService;
private final ColumnValidator columnValidator;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
@Autowired
public LoanReadPlatformServiceImpl(final PlatformSecurityContext context,
@@ -170,6 +173,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService,
final ConfigurationDomainService configurationDomainService,
final DelinquencyReadPlatformService delinquencyReadPlatformService,
final AccountDetailsReadPlatformService accountDetailsReadPlatformService, final LoanRepositoryWrapper loanRepositoryWrapper,
final ColumnValidator columnValidator, DatabaseSpecificSQLGenerator sqlGenerator, PaginationHelper paginationHelper) {
this.context = context;
@@ -193,9 +197,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.configurationDomainService = configurationDomainService;
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
this.columnValidator = columnValidator;
this.loaanLoanMapper = new LoanMapper(sqlGenerator);
this.loaanLoanMapper = new LoanMapper(sqlGenerator, delinquencyReadPlatformService);
this.sqlGenerator = sqlGenerator;
this.paginationHelper = paginationHelper;
this.delinquencyReadPlatformService = delinquencyReadPlatformService;
}
@Override
@@ -206,7 +211,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final String hierarchy = currentUser.getOffice().getHierarchy();
final String hierarchySearchString = hierarchy + "%";
final LoanMapper rm = new LoanMapper(sqlGenerator);
final LoanMapper rm = new LoanMapper(sqlGenerator, delinquencyReadPlatformService);
final StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("select ");
@@ -226,7 +231,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
// final AppUser currentUser = this.context.authenticatedUser();
this.context.authenticatedUser();
final LoanMapper rm = new LoanMapper(sqlGenerator);
final LoanMapper rm = new LoanMapper(sqlGenerator, delinquencyReadPlatformService);
final String sql = "select " + rm.loanSchema() + " where l.account_no=?";
@@ -237,7 +242,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public List<LoanAccountData> retrieveGLIMChildLoansByGLIMParentAccount(String parentloanAccountNumber) {
this.context.authenticatedUser();
final LoanMapper rm = new LoanMapper(sqlGenerator);
final LoanMapper rm = new LoanMapper(sqlGenerator, delinquencyReadPlatformService);
final String sql = "select " + rm.loanSchema()
+ " left join glim_parent_child_mapping as glim on glim.glim_child_account_id=l.account_no "
@@ -490,8 +495,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
@Override
public LoanTransactionData retrieveWaiveInterestDetails(final Long loanId) {
AppUser currentUser = this.context.authenticatedUser();
// TODO - KW -OPTIMIZE - write simple sql query to fetch back overdue
// interest that can be waived along with the date of repayment period
// interest is overdue.
@@ -582,9 +585,11 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private static final class LoanMapper implements RowMapper<LoanAccountData> {
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
LoanMapper(DatabaseSpecificSQLGenerator sqlGenerator) {
LoanMapper(DatabaseSpecificSQLGenerator sqlGenerator, DelinquencyReadPlatformService delinquencyReadPlatformService) {
this.sqlGenerator = sqlGenerator;
this.delinquencyReadPlatformService = delinquencyReadPlatformService;
}
public String loanSchema() {
@@ -989,6 +994,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final Long closureLoanId = rs.getLong("closureLoanId");
final String closureLoanAccountNo = rs.getString("closureLoanAccountNo");
final BigDecimal topupAmount = rs.getBigDecimal("topupAmount");
// Current Delinquency Range Data
DelinquencyRangeData delinquencyRange = this.delinquencyReadPlatformService.retrieveCurrentDelinquencyTag(id);
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName,
clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription,
@@ -1004,7 +1011,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData,
createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, maximumGap, loanSubStatus,
canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization,
fixedPrincipalPercentagePerInstallment);
fixedPrincipalPercentagePerInstallment, delinquencyRange);
}
}
@@ -141,5 +141,6 @@ public interface LoanProductConstants {
String ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED = "allowApprovedDisbursedAmountsOverApplied";
String OVER_APPLIED_CALCULATION_TYPE = "overAppliedCalculationType";
String OVER_APPLIED_NUMBER = "overAppliedNumber";
String DELINQUENCY_BUCKET_PARAM_NAME = "delinquencyBucketId";
}
@@ -65,6 +65,8 @@ import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformSer
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.fund.data.FundData;
@@ -130,6 +132,7 @@ public class LoanProductsApiResource {
private final FloatingRatesReadPlatformService floatingRateReadPlatformService;
private final RateReadService rateReadService;
private final ConfigurationDomainService configurationDomainService;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
@Autowired
public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService,
@@ -145,7 +148,8 @@ public class LoanProductsApiResource {
final DropdownReadPlatformService commonDropdownReadPlatformService,
PaymentTypeReadPlatformService paymentTypeReadPlatformService,
final FloatingRatesReadPlatformService floatingRateReadPlatformService, final RateReadService rateReadService,
final ConfigurationDomainService configurationDomainService) {
final ConfigurationDomainService configurationDomainService,
final DelinquencyReadPlatformService delinquencyReadPlatformService) {
this.context = context;
this.loanProductReadPlatformService = readPlatformService;
this.chargeReadPlatformService = chargeReadPlatformService;
@@ -164,6 +168,7 @@ public class LoanProductsApiResource {
this.floatingRateReadPlatformService = floatingRateReadPlatformService;
this.rateReadService = rateReadService;
this.configurationDomainService = configurationDomainService;
this.delinquencyReadPlatformService = delinquencyReadPlatformService;
}
@POST
@@ -339,6 +344,12 @@ public class LoanProductsApiResource {
if (fundOptions.isEmpty()) {
fundOptions = null;
}
Collection<DelinquencyBucketData> delinquencyBucketOptions = this.delinquencyReadPlatformService.retrieveAllDelinquencyBuckets();
if (delinquencyBucketOptions.isEmpty()) {
delinquencyBucketOptions = null;
}
final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions = this.dropdownReadPlatformService
.retreiveTransactionProcessingStrategies();
@@ -372,7 +383,7 @@ public class LoanProductsApiResource {
loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions,
interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions,
preCloseInterestCalculationStrategyOptions, floatingRateOptions, interestRecalculationNthDayTypeOptions,
interestRecalculationDayOfWeekTypeOptions, isRatesEnabled);
interestRecalculationDayOfWeekTypeOptions, isRatesEnabled, delinquencyBucketOptions);
}
}
@@ -39,6 +39,7 @@ import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateData;
import org.apache.fineract.portfolio.fund.data.FundData;
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
@@ -51,6 +52,7 @@ import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.rate.data.RateData;
import org.springframework.util.CollectionUtils;
@SuppressWarnings("unused")
/**
* Immutable data object to represent loan products.
*/
@@ -140,9 +142,7 @@ public class LoanProductData implements Serializable {
// template related
private final Collection<FundData> fundOptions;
@SuppressWarnings("unused")
private final Collection<PaymentTypeData> paymentTypeOptions;
@SuppressWarnings("unused")
private final Collection<CurrencyData> currencyOptions;
private final List<EnumOptionData> repaymentFrequencyTypeOptions;
private final List<EnumOptionData> interestRateFrequencyTypeOptions;
@@ -152,32 +152,19 @@ public class LoanProductData implements Serializable {
private final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions;
private final Collection<ChargeData> chargeOptions;
private final Collection<RateData> rateOptions;
@SuppressWarnings("unused")
private final Collection<ChargeData> penaltyOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> accountingRuleOptions;
@SuppressWarnings("unused")
private final Map<String, List<GLAccountData>> accountingMappingOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> valueConditionTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> daysInMonthTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> daysInYearTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRecalculationCompoundingTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRecalculationNthDayTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> rescheduleStrategyTypeOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> preClosureInterestCalculationStrategyOptions;
@SuppressWarnings("unused")
private final List<EnumOptionData> interestRecalculationFrequencyTypeOptions;
@SuppressWarnings("unused")
private final List<FloatingRateData> floatingRateOptions;
private final Boolean multiDisburseLoan;
@@ -198,6 +185,10 @@ public class LoanProductData implements Serializable {
private final boolean isEqualAmortization;
private final BigDecimal fixedPrincipalPercentagePerInstallment;
// Delinquency Buckets
private final Collection<DelinquencyBucketData> delinquencyBucketOptions;
private final DelinquencyBucketData delinquencyBucket;
/**
* Used when returning lookup information about loan product for dropdowns.
*/
@@ -282,6 +273,9 @@ public class LoanProductData implements Serializable {
final Collection<RateData> rateOptions = null;
final Collection<RateData> rates = null;
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType,
@@ -298,7 +292,7 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment);
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
}
@@ -384,6 +378,8 @@ public class LoanProductData implements Serializable {
final Collection<RateData> rateOptions = null;
final Collection<RateData> rates = null;
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -401,7 +397,7 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment);
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
}
@@ -494,6 +490,8 @@ public class LoanProductData implements Serializable {
final Collection<RateData> rateOptions = null;
final Collection<RateData> rates = null;
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -511,7 +509,7 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment);
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
}
@@ -598,6 +596,8 @@ public class LoanProductData implements Serializable {
final Collection<RateData> rateOptions = null;
final Collection<RateData> rates = null;
final boolean isRatesEnabled = false;
final Collection<DelinquencyBucketData> delinquencyBucketOptions = null;
final DelinquencyBucketData delinquencyBucket = null;
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -615,7 +615,7 @@ public class LoanProductData implements Serializable {
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap,
syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled,
fixedPrincipalPercentagePerInstallment);
fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
}
@@ -660,7 +660,8 @@ public class LoanProductData implements Serializable {
final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments,
final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization,
Collection<RateData> rateOptions, Collection<RateData> rates, final boolean isRatesEnabled,
final BigDecimal fixedPrincipalPercentagePerInstallment) {
final BigDecimal fixedPrincipalPercentagePerInstallment, final Collection<DelinquencyBucketData> delinquencyBucketOptions,
final DelinquencyBucketData delinquencyBucket) {
this.id = id;
this.name = name;
this.shortName = shortName;
@@ -774,7 +775,8 @@ public class LoanProductData implements Serializable {
this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate;
this.canUseForTopup = canUseForTopup;
this.isEqualAmortization = isEqualAmortization;
this.delinquencyBucketOptions = delinquencyBucketOptions;
this.delinquencyBucket = delinquencyBucket;
}
public LoanProductData(final LoanProductData productData, final Collection<ChargeData> chargeOptions,
@@ -790,7 +792,8 @@ public class LoanProductData implements Serializable {
final List<EnumOptionData> rescheduleStrategyTypeOptions, final List<EnumOptionData> interestRecalculationFrequencyTypeOptions,
final List<EnumOptionData> preCloseInterestCalculationStrategyOptions, final List<FloatingRateData> floatingRateOptions,
final List<EnumOptionData> interestRecalculationNthDayTypeOptions,
final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions, final boolean isRatesEnabled) {
final List<EnumOptionData> interestRecalculationDayOfWeekTypeOptions, final boolean isRatesEnabled,
final Collection<DelinquencyBucketData> delinquencyBucketOptions) {
this.id = productData.id;
this.name = productData.name;
this.shortName = productData.shortName;
@@ -921,6 +924,8 @@ public class LoanProductData implements Serializable {
this.isEqualAmortization = productData.isEqualAmortization;
this.rates = productData.rates;
this.isRatesEnabled = isRatesEnabled;
this.delinquencyBucketOptions = delinquencyBucketOptions;
this.delinquencyBucket = productData.delinquencyBucket;
}
private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) {
@@ -1051,6 +1056,10 @@ public class LoanProductData implements Serializable {
return this.fundOptions;
}
public Collection<DelinquencyBucketData> getDelinquencyBucketOptions() {
return delinquencyBucketOptions;
}
public List<EnumOptionData> getAmortizationTypeOptions() {
return this.amortizationTypeOptions;
}
@@ -54,6 +54,7 @@ import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
@@ -195,6 +196,10 @@ public class LoanProduct extends AbstractPersistableCustom {
@Column(name = "over_applied_number", nullable = true)
private Integer overAppliedNumber;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "delinquency_bucket_id", nullable = true)
private DelinquencyBucket delinquencyBucket;
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
final List<Rate> productRates) {
@@ -1001,6 +1006,15 @@ public class LoanProduct extends AbstractPersistableCustom {
this.syncExpectedWithDisbursementDate = newValue;
}
Long delinquencyBucketId = null;
if (this.delinquencyBucket != null) {
delinquencyBucketId = this.delinquencyBucket.getId();
}
if (command.isChangeInLongParameterNamed(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME, delinquencyBucketId)) {
final Long newValue = command.longValueOfParameterNamed(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME);
actualChanges.put(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME, newValue);
}
/**
* Update interest recalculation settings
*/
@@ -1578,4 +1592,12 @@ public class LoanProduct extends AbstractPersistableCustom {
this.loanProducTrancheDetails = loanProducTrancheDetails;
}
public DelinquencyBucket getDelinquencyBucket() {
return delinquencyBucket;
}
public void setDelinquencyBucket(DelinquencyBucket delinquencyBucket) {
this.delinquencyBucket = delinquencyBucket;
}
}
@@ -19,6 +19,7 @@
package org.apache.fineract.portfolio.loanproduct.domain;
import java.util.List;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query;
@@ -28,4 +29,6 @@ public interface LoanProductRepository extends JpaRepository<LoanProduct, Long>,
@Query("select loanProduct from LoanProduct loanProduct, IN(loanProduct.charges) charge where charge.id = :chargeId")
List<LoanProduct> retrieveLoanProductsByChargeId(@Param("chargeId") Long chargeId);
Long countByDelinquencyBucket(DelinquencyBucket delinquencyBucket);
}
@@ -111,7 +111,7 @@ public final class LoanProductDataValidator {
LoanProductConstants.CAN_USE_FOR_TOPUP, LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, LoanProductConstants.RATES_PARAM_NAME,
LoanProductConstants.fixedPrincipalPercentagePerInstallmentParamName, LoanProductConstants.DISALLOW_EXPECTED_DISBURSEMENTS,
LoanProductConstants.ALLOW_APPROVED_DISBURSED_AMOUNTS_OVER_APPLIED, LoanProductConstants.OVER_APPLIED_CALCULATION_TYPE,
LoanProductConstants.OVER_APPLIED_NUMBER));
LoanProductConstants.OVER_APPLIED_NUMBER, LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME));
private static final String[] supportedloanConfigurableAttributes = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyIdParamName,
@@ -274,6 +274,13 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter("transactionProcessingStrategyId").value(transactionProcessingStrategyId).notNull()
.integerGreaterThanZero();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME, element)) {
final Long delinquencyBucketId = this.fromApiJsonHelper.extractLongNamed(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
element);
baseDataValidator.reset().parameter(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME).value(delinquencyBucketId)
.ignoreIfNull().integerGreaterThanZero();
}
// grace validation
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment).zeroOrPositiveAmount();
@@ -1087,6 +1094,13 @@ public final class LoanProductDataValidator {
.integerZeroOrGreater();
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME, element)) {
final Long delinquencyBucketId = this.fromApiJsonHelper.extractLongNamed(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME,
element);
baseDataValidator.reset().parameter(LoanProductConstants.DELINQUENCY_BUCKET_PARAM_NAME).value(delinquencyBucketId)
.ignoreIfNull().integerGreaterThanZero();
}
Integer amortizationType = null;
if (this.fromApiJsonHelper.parameterExists("amortizationType", element)) {
amortizationType = this.fromApiJsonHelper.extractIntegerNamed("amortizationType", element, Locale.getDefault());
@@ -36,6 +36,8 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.charge.data.ChargeData;
import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.common.service.CommonEnumerations;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductData;
import org.apache.fineract.portfolio.loanproduct.data.LoanProductGuaranteeData;
@@ -60,18 +62,20 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
private final RateReadService rateReadService;
private final DatabaseSpecificSQLGenerator sqlGenerator;
private final FineractEntityAccessUtil fineractEntityAccessUtil;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
@Autowired
public LoanProductReadPlatformServiceImpl(final PlatformSecurityContext context,
final ChargeReadPlatformService chargeReadPlatformService, final JdbcTemplate jdbcTemplate,
final FineractEntityAccessUtil fineractEntityAccessUtil, final RateReadService rateReadService,
DatabaseSpecificSQLGenerator sqlGenerator) {
final DelinquencyReadPlatformService delinquencyReadPlatformService, final DatabaseSpecificSQLGenerator sqlGenerator) {
this.context = context;
this.chargeReadPlatformService = chargeReadPlatformService;
this.jdbcTemplate = jdbcTemplate;
this.fineractEntityAccessUtil = fineractEntityAccessUtil;
this.rateReadService = rateReadService;
this.sqlGenerator = sqlGenerator;
this.delinquencyReadPlatformService = delinquencyReadPlatformService;
}
@Override
@@ -82,7 +86,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final Collection<RateData> rates = this.rateReadService.retrieveProductLoanRates(loanProductId);
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas = retrieveLoanProductBorrowerCycleVariations(
loanProductId);
final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates);
final Collection<DelinquencyBucketData> delinquencyBucketOptions = this.delinquencyReadPlatformService
.retrieveAllDelinquencyBuckets();
final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates, delinquencyBucketOptions);
final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?";
return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanProductId }); // NOSONAR
@@ -104,7 +110,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
this.context.authenticatedUser();
final LoanProductMapper rm = new LoanProductMapper(null, null, null);
final LoanProductMapper rm = new LoanProductMapper(null, null, null, null);
String sql = "select " + rm.loanProductSchema();
@@ -183,11 +189,15 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
private final Collection<RateData> rates;
private final Collection<DelinquencyBucketData> delinquencyBucketOptions;
LoanProductMapper(final Collection<ChargeData> charges,
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas, final Collection<RateData> rates) {
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas, final Collection<RateData> rates,
final Collection<DelinquencyBucketData> delinquencyBucketOptions) {
this.charges = charges;
this.borrowerCycleVariationDatas = borrowerCycleVariationDatas;
this.rates = rates;
this.delinquencyBucketOptions = delinquencyBucketOptions;
}
public String loanProductSchema() {
@@ -235,7 +245,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lfr.max_differential_lending_rate as maxDifferentialLendingRate, "
+ "lfr.is_floating_interest_rate_calculation_allowed as isFloatingInterestRateCalculationAllowed, "
+ "lp.allow_variabe_installments as isVariableIntallmentsAllowed, " + "lvi.minimum_gap as minimumGap, "
+ "lvi.maximum_gap as maximumGap, "
+ "lvi.maximum_gap as maximumGap, dbuc.id as delinquencyBucketId, dbuc.name as delinquencyBucketName, "
+ "lp.can_use_for_topup as canUseForTopup, lp.is_equal_amortization as isEqualAmortization "
+ " from m_product_loan lp " + " left join m_fund f on f.id = lp.fund_id "
+ " left join m_product_loan_recalculation_details lpr on lpr.product_id=lp.id "
@@ -245,6 +255,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ " left join m_product_loan_floating_rates as lfr on lfr.loan_product_id = lp.id "
+ " left join m_floating_rates as fr on lfr.floating_rates_id = fr.id "
+ " left join m_product_loan_variable_installment_config as lvi on lvi.loan_product_id = lp.id "
+ " left join m_delinquency_bucket as dbuc on dbuc.id = lp.delinquency_bucket_id "
+ " join m_currency curr on curr.code = lp.currency_code";
}
@@ -465,6 +476,12 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
final Collection<RateData> rateOptions = null;
final boolean isRatesEnabled = false;
// Delinquency Buckets
final Long delinquencyBucketId = rs.getLong("delinquencyBucketId");
final String delinquencyBucketName = rs.getString("delinquencyBucketName");
final DelinquencyBucketData delinquencyBucket = new DelinquencyBucketData(delinquencyBucketId, delinquencyBucketName,
new ArrayList<>());
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType,
@@ -482,7 +499,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate,
maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap,
maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates,
isRatesEnabled, fixedPrincipalPercentagePerInstallment);
isRatesEnabled, fixedPrincipalPercentagePerInstallment, delinquencyBucketOptions, delinquencyBucket);
}
}
@@ -558,7 +575,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
public Collection<LoanProductData> retrieveAllLoanProductsForCurrency(String currencyCode) {
this.context.authenticatedUser();
final LoanProductMapper rm = new LoanProductMapper(null, null, null);
final LoanProductMapper rm = new LoanProductMapper(null, null, null, null);
String sql = "select " + rm.loanProductSchema() + " where lp.currency_code= ? ";
@@ -38,6 +38,8 @@ import org.apache.fineract.portfolio.businessevent.domain.loan.product.LoanProdu
import org.apache.fineract.portfolio.businessevent.service.BusinessEventNotifierService;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate;
import org.apache.fineract.portfolio.floatingrates.domain.FloatingRateRepositoryWrapper;
import org.apache.fineract.portfolio.fund.domain.Fund;
@@ -83,6 +85,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
private final FloatingRateRepositoryWrapper floatingRateRepository;
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final BusinessEventNotifierService businessEventNotifierService;
private final DelinquencyBucketRepository delinquencyBucketRepository;
@Autowired
public LoanProductWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
@@ -92,7 +95,8 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
final ChargeRepositoryWrapper chargeRepository, final RateRepositoryWrapper rateRepository,
final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
final FineractEntityAccessUtil fineractEntityAccessUtil, final FloatingRateRepositoryWrapper floatingRateRepository,
final LoanRepositoryWrapper loanRepositoryWrapper, final BusinessEventNotifierService businessEventNotifierService) {
final LoanRepositoryWrapper loanRepositoryWrapper, final BusinessEventNotifierService businessEventNotifierService,
final DelinquencyBucketRepository delinquencyBucketRepository) {
this.context = context;
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanProductRepository = loanProductRepository;
@@ -106,6 +110,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
this.floatingRateRepository = floatingRateRepository;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.businessEventNotifierService = businessEventNotifierService;
this.delinquencyBucketRepository = delinquencyBucketRepository;
}
@Transactional
@@ -215,6 +220,12 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
product.update(fund);
}
if (changes.containsKey("delinquencyBucketId")) {
final Long delinquencyBucketId = (Long) changes.get("delinquencyBucketId");
final DelinquencyBucket delinquencyBucket = this.delinquencyBucketRepository.getReferenceById(delinquencyBucketId);
product.setDelinquencyBucket(delinquencyBucket);
}
if (changes.containsKey("transactionProcessingStrategyId")) {
final Long transactionProcessingStrategyId = (Long) changes.get("transactionProcessingStrategyId");
final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy = findStrategyByIdIfProvided(
@@ -72,7 +72,7 @@ public class GenerateAdhocClientScheduleTasklet implements Tasklet {
run = Math.toIntExact(ChronoUnit.YEARS.between(start, end)) >= 1;
}
case CUSTOM -> {
next = start.plusDays((int) (long) adhoc.getReportRunEvery());
next = start.plusDays((long) adhoc.getReportRunEvery());
run = Math.toIntExact(ChronoUnit.DAYS.between(start, end)) >= adhoc.getReportRunEvery();
}
}
@@ -560,6 +560,10 @@ public class AppUser extends AbstractPersistableCustom implements PlatformUser {
validateHasPermission("UPDATE", resourceType);
}
public void validateHasDeletePermission(final String resourceType) {
validateHasPermission("DELETE", resourceType);
}
private void validateHasPermission(final String prefix, final String resourceType) {
final String authorizationMessage = "User has no authority to " + prefix + " " + resourceType.toLowerCase() + "s";
final String matchPermission = prefix + "_" + resourceType.toUpperCase();
@@ -48,4 +48,5 @@
<include file="parts/0026_reversals_for_reversed_transactions.xml" relativeToChangelogFile="true"/>
<include file="parts/0027_add_charge_refund_permission.xml" relativeToChangelogFile="true"/>
<include file="parts/0028_add_charge_refund_charge_type_to_loan_transaction.xml" relativeToChangelogFile="true"/>
<include file="parts/0029_add_delinquency_buckets.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,325 @@
<?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">
<changeSet author="fineract" id="1" context="mysql">
<createTable tableName="m_delinquency_range">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="classification" type="VARCHAR(100)">
<constraints unique="true" nullable="false"/>
</column>
<column name="min_age_days" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="max_age_days" type="BIGINT">
<constraints nullable="true" />
</column>
<column name="created_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_on_utc" type="DATETIME">
<constraints nullable="false" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="last_modified_on_utc" type="DATETIME">
<constraints nullable="false" />
</column>
</createTable>
<createTable tableName="m_delinquency_bucket">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="name" type="VARCHAR(100)">
<constraints unique="true" nullable="false"/>
</column>
<column name="created_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_on_utc" type="DATETIME">
<constraints nullable="false" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="last_modified_on_utc" type="DATETIME">
<constraints nullable="false" />
</column>
</createTable>
<createTable tableName="m_delinquency_bucket_mappings">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="delinquency_range_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="delinquency_bucket_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_on_utc" type="DATETIME">
<constraints nullable="false" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="last_modified_on_utc" type="DATETIME">
<constraints nullable="false" />
</column>
</createTable>
<createTable tableName="m_loan_delinquency_tag_history">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="delinquency_range_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="loan_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="addedon_date" type="DATE">
<constraints nullable="false" />
</column>
<column defaultValueComputed="NULL" name="liftedon_date" type="DATE" />
<column name="created_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_on_utc" type="DATETIME">
<constraints nullable="false" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="last_modified_on_utc" type="DATETIME">
<constraints nullable="false" />
</column>
</createTable>
</changeSet>
<changeSet author="fineract" id="1" context="postgresql">
<createTable tableName="m_delinquency_range">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="classification" type="VARCHAR(100)">
<constraints unique="true" nullable="false"/>
</column>
<column name="min_age_days" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="max_age_days" type="BIGINT">
<constraints nullable="true" />
</column>
<column name="created_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false" />
</column>
</createTable>
<createTable tableName="m_delinquency_bucket">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="name" type="VARCHAR(100)">
<constraints unique="true" nullable="false"/>
</column>
<column name="created_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false" />
</column>
</createTable>
<createTable tableName="m_delinquency_bucket_mappings">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="delinquency_range_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="delinquency_bucket_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false" />
</column>
</createTable>
<createTable tableName="m_loan_delinquency_tag_history">
<column autoIncrement="true" name="id" type="BIGINT">
<constraints nullable="false" primaryKey="true"/>
</column>
<column name="delinquency_range_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="loan_id" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="addedon_date" type="DATE">
<constraints nullable="false" />
</column>
<column defaultValueComputed="NULL" name="liftedon_date" type="DATE" />
<column name="created_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="created_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false" />
</column>
<column name="version" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="last_modified_by" type="BIGINT">
<constraints nullable="false" />
</column>
<column name="last_modified_on_utc" type="TIMESTAMP WITH TIME ZONE">
<constraints nullable="false" />
</column>
</createTable>
</changeSet>
<changeSet author="fineract" id="2">
<addForeignKeyConstraint baseColumnNames="delinquency_range_id" baseTableName="m_delinquency_bucket_mappings"
constraintName="FK_m_delinquency_range_mapping" deferrable="false"
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="m_delinquency_range" validate="true"/>
<addForeignKeyConstraint baseColumnNames="delinquency_bucket_id" baseTableName="m_delinquency_bucket_mappings"
constraintName="FK_m_delinquency_bucket_mapping" deferrable="false"
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="m_delinquency_bucket" validate="true"/>
</changeSet>
<changeSet author="fineract" id="3">
<addColumn tableName="m_product_loan">
<column name="delinquency_bucket_id" type="BIGINT" defaultValueComputed="NULL"/>
</addColumn>
<addForeignKeyConstraint baseColumnNames="delinquency_bucket_id" baseTableName="m_product_loan"
constraintName="FK_m_product_loan_m_delinquency_bucket" deferrable="false"
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="m_delinquency_bucket" validate="true"/>
</changeSet>
<changeSet author="fineract" id="4">
<addForeignKeyConstraint baseColumnNames="delinquency_range_id" baseTableName="m_loan_delinquency_tag_history"
constraintName="FK_m_delinquency_tags_range" deferrable="false"
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="m_delinquency_range" validate="true"/>
<addForeignKeyConstraint baseColumnNames="loan_id" baseTableName="m_loan_delinquency_tag_history"
constraintName="FK_m_delinquency_tags_loan" deferrable="false"
initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT"
referencedColumnNames="id" referencedTableName="m_loan" validate="true"/>
</changeSet>
<changeSet author="fineract" id="5">
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="READ_DELINQUENCY_BUCKET"/>
<column name="entity_name" value="DELINQUENCY_BUCKET"/>
<column name="action_name" value="READ"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="CREATE_DELINQUENCY_BUCKET"/>
<column name="entity_name" value="DELINQUENCY_BUCKET"/>
<column name="action_name" value="CREATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="UPDATE_DELINQUENCY_BUCKET"/>
<column name="entity_name" value="DELINQUENCY_BUCKET"/>
<column name="action_name" value="UPDATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="DELETE_DELINQUENCY_BUCKET"/>
<column name="entity_name" value="DELINQUENCY_BUCKET"/>
<column name="action_name" value="DELETE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="CREATE_DELINQUENCY_RANGE"/>
<column name="entity_name" value="DELINQUENCY_RANGE"/>
<column name="action_name" value="CREATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="UPDATE_DELINQUENCY_RANGE"/>
<column name="entity_name" value="DELINQUENCY_RANGE"/>
<column name="action_name" value="UPDATE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
<insert tableName="m_permission">
<column name="grouping" value="organisation"/>
<column name="code" value="DELETE_DELINQUENCY_RANGE"/>
<column name="entity_name" value="DELINQUENCY_RANGE"/>
<column name="action_name" value="DELETE"/>
<column name="can_maker_checker" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -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.integrationtests;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.util.ArrayList;
import org.apache.fineract.client.models.DeleteDelinquencyBucketResponse;
import org.apache.fineract.client.models.DeleteDelinquencyRangeResponse;
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
import org.apache.fineract.client.models.PutDelinquencyBucketResponse;
import org.apache.fineract.client.models.PutDelinquencyRangeResponse;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
import org.apache.fineract.integrationtests.common.products.DelinquencyRangesHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
public class DelinquencyBucketsIntegrationTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
@BeforeEach
public void setup() {
Utils.initializeRESTAssured();
requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
}
@Test
public void testCreateDelinquencyRanges() {
// given
final String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
// when
final PostDelinquencyRangeResponse delinquencyRangeResponse01 = DelinquencyRangesHelper.createDelinquencyRange(requestSpec,
responseSpec, jsonRange);
final ArrayList<GetDelinquencyRangesResponse> ranges = DelinquencyRangesHelper.getDelinquencyRanges(requestSpec, responseSpec);
// then
assertNotNull(delinquencyRangeResponse01);
assertNotNull(ranges);
assertEquals(1, ranges.get(0).getMinimumAgeDays(), "Expected Min Age Days to 1");
assertEquals(3, ranges.get(0).getMaximumAgeDays(), "Expected Max Age Days to 3");
}
@Test
public void testUpdateDelinquencyRanges() {
// given
String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
final PostDelinquencyRangeResponse delinquencyRangeResponse01 = DelinquencyRangesHelper.createDelinquencyRange(requestSpec,
responseSpec, jsonRange);
jsonRange = DelinquencyRangesHelper.getAsJSON(1, 7);
assertNotNull(delinquencyRangeResponse01);
// when
final PutDelinquencyRangeResponse delinquencyRangeResponse02 = DelinquencyRangesHelper.updateDelinquencyRange(requestSpec,
responseSpec, delinquencyRangeResponse01.getResourceId(), jsonRange);
final GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
delinquencyRangeResponse01.getResourceId());
final DeleteDelinquencyRangeResponse deleteDelinquencyRangeResponse = DelinquencyRangesHelper.deleteDelinquencyRange(requestSpec,
responseSpec, delinquencyRangeResponse01.getResourceId());
// then
assertNotNull(delinquencyRangeResponse02);
assertNotNull(deleteDelinquencyRangeResponse);
assertNotNull(range);
assertNotEquals(3, range.getMaximumAgeDays());
assertEquals(1, range.getMinimumAgeDays());
assertEquals(7, range.getMaximumAgeDays());
}
@Test
public void testDelinquencyBuckets() {
// given
ArrayList<Integer> rangeIds = new ArrayList<>();
String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30);
// Create
delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
responseSpec, jsonBucket);
// Update
jsonRange = DelinquencyRangesHelper.getAsJSON(31, 60);
rangeIds.add(delinquencyRangeResponse.getResourceId());
jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
PutDelinquencyBucketResponse updateDelinquencyBucketResponse = DelinquencyBucketsHelper.updateDelinquencyBucket(requestSpec,
responseSpec, delinquencyBucketResponse.getResourceId(), jsonBucket);
delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
// Read
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
delinquencyBucketResponse.getResourceId());
// when
final ArrayList<GetDelinquencyBucketsResponse> bucketList = DelinquencyBucketsHelper.getDelinquencyBuckets(requestSpec,
responseSpec);
// then
assertNotNull(bucketList);
assertNotNull(delinquencyBucket);
assertEquals(2, delinquencyBucket.getRanges().size());
assertNotNull(delinquencyBucketResponse);
assertNotNull(updateDelinquencyBucketResponse);
}
@Test
public void testDelinquencyBucketDelete() {
// given
ArrayList<Integer> rangeIds = new ArrayList<>();
String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30);
// Create
delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
responseSpec, jsonBucket);
// Delete
DeleteDelinquencyBucketResponse deleteDelinquencyBucketResponse = DelinquencyBucketsHelper.deleteDelinquencyBucket(requestSpec,
responseSpec, delinquencyBucketResponse.getResourceId());
// when
final ArrayList<GetDelinquencyBucketsResponse> bucketList = DelinquencyBucketsHelper.getDelinquencyBuckets(requestSpec,
responseSpec);
// then
assertNotNull(bucketList);
assertNotNull(delinquencyBucketResponse);
assertNotNull(deleteDelinquencyBucketResponse);
}
@Test
public void testDelinquencyBucketsRangeAgeOverlaped() {
// Given
ArrayList<Integer> rangeIds = new ArrayList<>();
String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
jsonRange = DelinquencyRangesHelper.getAsJSON(3, 30);
// Create
delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
final ResponseSpecification response403Spec = new ResponseSpecBuilder().expectStatusCode(403).build();
// When
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, response403Spec, jsonBucket);
}
@Test
public void testDelinquencyBucketsNameDuplication() {
// Given
ArrayList<Integer> rangeIds = new ArrayList<>();
String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
jsonRange = DelinquencyRangesHelper.getAsJSON(4, 30);
// Create
delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
rangeIds.add(delinquencyRangeResponse.getResourceId());
String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
final ResponseSpecification response403Spec = new ResponseSpecBuilder().expectStatusCode(403).build();
// When
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, jsonBucket);
// Then
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, response403Spec, jsonBucket);
}
}
@@ -338,4 +338,9 @@ public final class Utils {
.spec(responseSpec).log().ifError().when().get(getURL).andReturn().asString();
return templateLocation.substring(1, templateLocation.length() - 1);
}
public static String emptyJson() {
return "{}";
}
}
@@ -0,0 +1,91 @@
/**
* 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.integrationtests.common.products;
import com.google.gson.Gson;
import com.linecorp.armeria.internal.shaded.guava.reflect.TypeToken;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.fineract.client.models.DeleteDelinquencyBucketResponse;
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
import org.apache.fineract.client.models.PutDelinquencyBucketResponse;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DelinquencyBucketsHelper {
private static final String DELINQUENCY_BUCKETS_URL = "/fineract-provider/api/v1/delinquency/buckets";
private static final Gson GSON = new JSON().getGson();
private static final Logger LOG = LoggerFactory.getLogger(DelinquencyBucketsHelper.class);
protected DelinquencyBucketsHelper() {}
public static ArrayList<GetDelinquencyBucketsResponse> getDelinquencyBuckets(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec) {
String response = Utils.performServerGet(requestSpec, responseSpec, DELINQUENCY_BUCKETS_URL + "?" + Utils.TENANT_IDENTIFIER);
Type delinquencyBucketListType = new TypeToken<ArrayList<GetDelinquencyBucketsResponse>>() {}.getType();
return GSON.fromJson(response, delinquencyBucketListType);
}
public static GetDelinquencyBucketsResponse getDelinquencyBucket(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final Integer resourceId) {
String response = Utils.performServerGet(requestSpec, responseSpec,
DELINQUENCY_BUCKETS_URL + "/" + resourceId + "?" + Utils.TENANT_IDENTIFIER);
return GSON.fromJson(response, GetDelinquencyBucketsResponse.class);
}
public static PostDelinquencyBucketResponse createDelinquencyBucket(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String json) {
LOG.info("JSON: {}", json);
final String response = Utils.performServerPost(requestSpec, responseSpec, DELINQUENCY_BUCKETS_URL + "?" + Utils.TENANT_IDENTIFIER,
json, null);
return GSON.fromJson(response, PostDelinquencyBucketResponse.class);
}
public static PutDelinquencyBucketResponse updateDelinquencyBucket(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final Integer resourceId, final String json) {
LOG.info("JSON: {}", json);
final String response = Utils.performServerPut(requestSpec, responseSpec,
DELINQUENCY_BUCKETS_URL + "/" + resourceId + "?" + Utils.TENANT_IDENTIFIER, json, null);
return GSON.fromJson(response, PutDelinquencyBucketResponse.class);
}
public static DeleteDelinquencyBucketResponse deleteDelinquencyBucket(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final Integer resourceId) {
final String response = Utils.performServerDelete(requestSpec, responseSpec,
DELINQUENCY_BUCKETS_URL + "/" + resourceId + "?" + Utils.TENANT_IDENTIFIER, Utils.emptyJson(), null);
return GSON.fromJson(response, DeleteDelinquencyBucketResponse.class);
}
public static String getAsJSON(final ArrayList<Integer> rangeIds) {
final HashMap<String, Object> map = new HashMap<>();
map.put("name", Utils.randomNameGenerator("Delinquency_Bucket_", 4));
map.put("ranges", rangeIds.toArray());
return new Gson().toJson(map);
}
}
@@ -0,0 +1,95 @@
/**
* 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.integrationtests.common.products;
import com.google.gson.Gson;
import com.linecorp.armeria.internal.shaded.guava.reflect.TypeToken;
import io.restassured.specification.RequestSpecification;
import io.restassured.specification.ResponseSpecification;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.fineract.client.models.DeleteDelinquencyRangeResponse;
import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
import org.apache.fineract.client.models.PutDelinquencyRangeResponse;
import org.apache.fineract.client.util.JSON;
import org.apache.fineract.integrationtests.common.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DelinquencyRangesHelper {
private static final String DELINQUENCY_RANGES_URL = "/fineract-provider/api/v1/delinquency/ranges";
private static final Gson GSON = new JSON().getGson();
private static final Logger LOG = LoggerFactory.getLogger(DelinquencyRangesHelper.class);
protected DelinquencyRangesHelper() {}
public static ArrayList<GetDelinquencyRangesResponse> getDelinquencyRanges(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec) {
String response = Utils.performServerGet(requestSpec, responseSpec, DELINQUENCY_RANGES_URL + "?" + Utils.TENANT_IDENTIFIER);
Type delinquencyRangeListType = new TypeToken<ArrayList<GetDelinquencyRangesResponse>>() {}.getType();
return GSON.fromJson(response, delinquencyRangeListType);
}
public static GetDelinquencyRangesResponse getDelinquencyRange(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final Integer resourceId) {
String response = Utils.performServerGet(requestSpec, responseSpec,
DELINQUENCY_RANGES_URL + "/" + resourceId + "?" + Utils.TENANT_IDENTIFIER);
LOG.info("----- {}", response);
return GSON.fromJson(response, GetDelinquencyRangesResponse.class);
}
public static PostDelinquencyRangeResponse createDelinquencyRange(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final String json) {
final String response = Utils.performServerPost(requestSpec, responseSpec, DELINQUENCY_RANGES_URL + "?" + Utils.TENANT_IDENTIFIER,
json, null);
LOG.info("----- {}", response);
return GSON.fromJson(response, PostDelinquencyRangeResponse.class);
}
public static PutDelinquencyRangeResponse updateDelinquencyRange(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final Integer resourceId, final String json) {
final String response = Utils.performServerPut(requestSpec, responseSpec,
DELINQUENCY_RANGES_URL + "/" + resourceId + "?" + Utils.TENANT_IDENTIFIER, json, null);
LOG.info("----- {}", response);
return GSON.fromJson(response, PutDelinquencyRangeResponse.class);
}
public static DeleteDelinquencyRangeResponse deleteDelinquencyRange(final RequestSpecification requestSpec,
final ResponseSpecification responseSpec, final Integer resourceId) {
final String response = Utils.performServerDelete(requestSpec, responseSpec,
DELINQUENCY_RANGES_URL + "/" + resourceId + "?" + Utils.TENANT_IDENTIFIER, Utils.emptyJson(), null);
LOG.info("----- {}", response);
return GSON.fromJson(response, DeleteDelinquencyRangeResponse.class);
}
public static String getAsJSON(int minimumAgeDays, int maximumAgeDays) {
final HashMap<String, Object> map = new HashMap<>();
map.put("classification", Utils.randomNameGenerator("Delinquency_", 4));
map.put("minimumAgeDays", minimumAgeDays);
map.put("maximumAgeDays", maximumAgeDays);
map.put("locale", "en");
return new Gson().toJson(map);
}
}